0034-方法區

1. 棧、堆、方法區的交互關係

  1. 線程共享的角度
    在這裏插入圖片描述
1. 線程共享區域:堆和方法區,兩者都會有OutOfMemoryError和GC

2. 線程獨佔區域:虛擬機棧,本地方法棧,程序計數器,兩個棧會出現StackOverFlowErr,都沒有GC
  1. 對象聲明涉及到的棧堆方法區
    在這裏插入圖片描述

2. 方法區的理解

  1. 簡述
1. 方法區(Method Area)與Java堆一樣,是各個線程共享的內存區域

2. 方法區在JVM啓動的時候被創建,實際的物理內存和Java堆區一樣都可以是不連續的

3. 方法區的大小跟堆空間一樣,可以選擇固定大小或者可擴展

4. 方法區的大小決定了系統可以保存多少個類,類加載太多會導致內存溢出
(java.lang.OutOfMemoryError:PermGen space jdk7及以前或者java.lang.OutOfMemoryError:Metaspace jdk8及以後)
   4.1 加載大量第三方的jar包
   4.2 Tomcat部署工程過多(30-50個)
   4.3 大量動態代理生成反射類
 
5. 關閉jvm會釋放這個區域的內存

3. 方法區的演進

1. 方法區是java虛擬機規範中的概念,相當於接口,永久代和元空間是hotspot虛擬機的實現

2. jdk7及以前方法區被稱爲永久代,jdk8及以後使用元空間取代了永久代

3. 永久代的方法區使用的是虛擬機內存,容易出現oom(超過XX:MaxPermSize上限),元空間使用的是本地內存(默認只受本地內存限制)

在這裏插入圖片描述

4. 設置方法區大小

  1. jdk7及以前
1. 通過-XX:PermSize來設置永久代初始分配空間,默認值是20.75

2. -XX:MaxPermSize來設定永久代最大可分配空間
32位機器默認是64M,64機器默認是82M

3. jvm加載的類信息超過了MaxPermSize會包OutOfMemoryError:PermGen space

查看java進程的參數配置
在這裏插入圖片描述

  1. jdk1.8及以後
1. 元數據區大小可以使用參數-XX:MetaspaceSize和-XX:MaxMetaspaceSize指定

2. windows下,-XX:MetaspaceSize是21M,-XX:MaxMetaspaceSize的值是-1,表示沒有限制

3. 默認情況下,虛擬機會耗盡所有的可用內存,如果元數據發生溢出,還是會有OutOfMemory:Metaspace的異常

4. --XX:MetaspaceSize 設置初始的元空間大小,對於64位的服務器端jvm來說,其默認值爲21M,這就是高水位線,
一但觸及這個水位線,FullGC將會被觸發,卸載沒用的類,然後高水位線將被重置。
新的高水位線的值取決於GC後釋放了多少元空間,如果釋放的空間不足,那麼在不超過MaxMetaspaceSize時,適當提高這個值,相反則適當降低這個值

5. 方法區的內部結構

  1. 簡述
    在這裏插入圖片描述
    在這裏插入圖片描述

方法區用於存儲已被虛擬機加載的類型信息、常量、靜態變量、即時編譯後的代碼緩存等——《深入理解Java虛擬機》

5.1 類型信息

對每個加載的類型包括類class、接口interface、枚舉enum、註解annotation,JVM必須在方法區中存儲以下類型的消息

1. 這個類型的完整有效名稱(全名=包名.類名)

2. 這個類型直接父類的完整有效名(對於interface或是java.lang.Object,都沒有父類)

3. 這個類型的修飾符(public,abstract,final的某個子集)

4. 這個類型直接接口的一個有序列表

5.2 域(Field)信息

1. JVM必須在方法區中保存類型的所有域的相關信息以及域的聲明順序

2. 域的相關信息包括:域名稱、域類型、域修飾符
(public,private,protected,static,final,volatile,transient的某個子集)

5.3 方法(Method)信息

1. 方法名稱

2. 方法的返回類型(或void)

3. 方法的參數的數量和類型(按順序)

4. 方法的修飾符(public,private,protected,static,final,synchronized,native,abstract的一個子集)

5. 方法的字節碼(bytecodes)、操作數棧、局部變量表及大小(abstract和native方法除外)

6. 異常表(abstract和native方法除外)
    每個異常處理的開始位置、結束位置、代碼處理在程序計數器中的偏移地址
    被捕獲的異常類的常量池索引

5.4 類變量

  1. non-final的類變量
1. 靜態變量和類關聯在一起,隨着類的加載而加載(準備階段零值初始化,初始化階段真實賦值)

2. 類變量被類的所有實例共享,即使沒有實例也可以訪問

在這裏插入圖片描述

  1. static-final變量
    被聲明爲final的類變量的處理方法則不同,每個全局變量在編譯的時候就會被分配了。
1. 八種基礎數據類型和字符串常量,在編譯期間直接確定

2. final對象創建會和static變量一起賦值,在static代碼塊中賦值
    // 八種基礎數據類型和字符串常量在編譯期就被確定
    public final static byte a1 = 1;
    public final static short a2 = 2;
    public final static char a3 = 3;
    public final static int a4 = 4;
    public final static long a5 = 5;
    public final static boolean a6 = false;
    public final static float a7 = 1.0f;
    public final static double a8 = 2.0;

    private static final String str = "測試方法的內部結構";
    
    // 字節碼
  public static final byte a1;
    descriptor: B
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: int 1

  public static final short a2;
    descriptor: S
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: int 2

  public static final char a3;
    descriptor: C
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: int 3

  public static final int a4;
    descriptor: I
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: int 4

  public static final long a5;
    descriptor: J
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: long 5l

  public static final boolean a6;
    descriptor: Z
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: int 0

  public static final float a7;
    descriptor: F
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: float 1.0f

  public static final double a8;
    descriptor: D
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: double 2.0d

  private static final java.lang.String str;
    descriptor: Ljava/lang/String;
    flags: ACC_PRIVATE, ACC_STATIC, ACC_FINAL
    ConstantValue: String 測試方法的內部結構

    // 引用類型 在static代碼塊中賦值
    private static final String s2 = new String("測試");
    private static final ATest aTest = new ATest();
    
    // 字節碼    
  private static final java.lang.String s2;
    descriptor: Ljava/lang/String;
    flags: ACC_PRIVATE, ACC_STATIC, ACC_FINAL

  private static final ATest aTest;
    descriptor: LATest;
    flags: ACC_PRIVATE, ACC_STATIC, ACC_FINAL

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=3, locals=0, args_size=0
         0: new           #14                 // class java/lang/String
         3: dup
         4: ldc           #16                 // String 測試
         6: invokespecial #17                 // Method java/lang/String."<init>":(Ljava/lang/String;)V
         9: putstatic     #18                 // Field s2:Ljava/lang/String;
        12: new           #19                 // class ATest
        15: dup
        16: invokespecial #20                 // Method ATest."<init>":()V
        19: putstatic     #12                 // Field aTest:LATest;
        22: return
      LineNumberTable:
        line 24: 0
        line 25: 12

5.5 運行時常量池 & 常量池

  1. 常量池
    常量池是字節碼文件的一部分,包括各種字面量和對類型、域和方法的符號引用


    在這裏插入圖片描述
    在這裏插入圖片描述

  2. 爲什麼需要常量池
    一個java源文件中的類、接口、編譯後產生一個字節碼文件。字節碼中有可能會有多個方法引用同一個類,如果在每個方法中都保存這個引用,會顯得很冗餘,java虛擬機把這個引用放到了常量池,其它方法都保存符號引用,指向常量池
    在這裏插入圖片描述

  3. 常量池中有什麼

1. 數量值

2. 字符串值

3. 類引用

4. 字段引用

5. 方法引用

常量池可以看做一張表,虛擬機指令根據這張常量表找到要執行的類名、方法名、參數類型、字面量等類型

  1. 運行時常量池
1. 運行時常量池是方法區的一部分

2. 常量池是Class文件的一部分,用於存放編譯期的各種字面量與符號引用,
這部分內容將在類加載後存放到方法區的運行時常量池中

3. 運行時常量池,在加載類和接口到虛擬機後,就會創建對應的運行時常量池

4. JVM爲每個已加載的類型(類或接口)都維護一個常量池。池中的數據項像數組項一樣,是通過索引訪問的

5. 運行時常量池中包含多種不同的常量,包括編譯期就已經明確的數值字面量,
也包括到運行期解析後才能夠獲得的方法或者字段引用,此時已經不再是常量池中的符號地址了,這裏換爲真實地址
    5.1 運行時常量池,相對於Class文件中的常量池的另一個重要特徵就是具備動態性,
    可以動態添加常量(String.intern())
    
6. 運行時常量池的數據要比常量池的數據更加豐富一些

7. 當創建類或接口的運行時常量池時,如果構建運行時常量池所需的內存空間
超過了最大能提供的空間,則JVM會拋OutOfMemoryError異常

6. 方法區的演進細節

永久代是HotSpot虛擬機中的概念,是java虛擬機規範方法區的落地實現

jdk版本 演進內容
jdk6及之前 有永久代(permanent generation),靜態變量存放在永久代上
jdk7 有永久代,但已經逐步“去永久代”,字符串常量池、靜態變量移除,保存在堆中
jdk8及以後 無永久代,類型信息、字段、方法、常量保存在本地內存的元空間,但字符串常量池、靜態變量仍在堆

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

  1. 永久代爲什麼要被元空間替換?
1. 永久代設置空間大小是很難確定的
    隨着空能的擴展,加載的類變多,容易出現oom
    而元空間和本地空間的區別在於:元空間使用的是本地內存,內存空間不受虛擬機內存大小控制,不容易出現oom

2. 對永久代的調優是很困難的
  1. StringTable爲什麼要調整?
jdk7將StringTable放到了堆空間中。因爲永久代的回收效率很低,
在full gc的時候纔會觸發,而full gc是老年代、永久代不足時纔會觸發。
這就導致StringTable回收效率不高。而我們開發中會有大量的字符串被創建,回收效率低,導致永久代內存不足,放在堆裏,能及時回收內存。
  1. 靜態變量放在哪裏?
/**
 * 結論:
 * 靜態引用對應的對象實體始終都存在堆空間
 *
 * jdk7:
 * -Xms200m -Xmx200m -XX:PermSize=300m -XX:MaxPermSize=300m -XX:+PrintGCDetails
 * jdk 8:
 * -Xms200m -Xmx200m -XX:MetaspaceSize=300m -XX:MaxMetaspaceSize=300m -XX:+PrintGCDetails
 * @author shkstart  [email protected]
 * @create 2020  21:20
 */
public class StaticFieldTest {
    private static byte[] arr = new byte[1024 * 1024 * 100];//100MB

    public static void main(String[] args) {
        System.out.println(StaticFieldTest.arr);

//        try {
//            Thread.sleep(1000000);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
    }
}

結論1
靜態引用對應的對象實體始終都存在堆空間,jdk678版本new byte[1024*1024*1024]都在堆上分配

/**
 * 《深入理解Java虛擬機》中的案例:
 * staticObj、instanceObj、localObj存放在哪裏?
 * @author shkstart  [email protected]
 * @create 2020  11:39
 */
public class StaticObjTest {
    static class Test {
        static ObjectHolder staticObj = new ObjectHolder();
        ObjectHolder instanceObj = new ObjectHolder();

        void foo() {
            ObjectHolder localObj = new ObjectHolder();
            System.out.println("done");
        }
    }

    private static class ObjectHolder {
    }

    public static void main(String[] args) {
        Test test = new StaticObjTest.Test();
        test.foo();
    }
}

結論2
三個new出來的對象,都存放在堆區,instanceObj跟隨Test對象實例放在堆區,localObj跟隨foo棧幀放在棧區,staticObj跟隨Class對象放在堆區

關於類的元數據和Class對象
參考

Class對象是存放在堆區的,不是方法區,這點很多人容易犯錯。類的元數據(元數據並不是類的Class對象!Class對象是加載的最終產品,類的方法代碼,變量名,方法名,訪問權限,返回值等等都是在方法區的)纔是存在方法區的。

7. 方法區的垃圾回收

  1. 主要回收內容
1. 常量池中廢棄的常量
    a) 字面量
        文本字符串
        被聲明爲final的常量
    b) 符號引用
        類和接口的全限定名
        字段的名稱和描述符
        方法的名稱和描述符

2. 不再使用的類
  1. 什麼時候回收
1. 常量池回收
常量池中的常量沒有被任何地方引用,就可以回收

2. 不再使用的類回收
    2.1 所有實例被回收
    2.2 加載該類的類加載器被回收
    2.3 Class對象沒有再任何地方被引用
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章