天天看點

JVM深入學習(七)-運作時資料區之方法區

5.1 方法區的了解

5.1.1 方法區、堆、棧的互動關系

User user = new User()

  1. User 就是方法區,存儲類的資訊
  2. user 就是棧,存儲引用
  3. new User()就是堆,配置設定對象空間

5.1.2 概述

方法區相當于接口,jdk7中的實作被稱為永久代,jdk8中的實作被稱為元空間

方法區邏輯上屬于堆的一部分,但是實際情況中可以把堆和方法區區分開,方法區又稱之為 非堆(non-Heap),而且實際上堆的jvm參數設定大小也并不包括方法區

  1. 方法區與堆類似,都屬于多線程共享的
  2. 方法區在jvm啟動時建立,同樣的與堆類似,在實體上可以是不連續的
  3. 方法區的大小與堆類似,可以設定為固定大小/動态擴充
  4. 方法區大小決定了系統可以建立多少類,如果類太多,方法區就會報 OOM: Jdk7 PermGen space jdk8 MetaSpace
  5. 關閉jvm就會銷毀方法區,釋放方法區的記憶體

5.1.3 方法區的演變

jdk7 方法區被稱為永久代

jdk8 方法區被稱為元空間

  • 嚴格來說:方法區≠永久代,僅僅對hotspot來說有永久代的概念,J9/JRocket中都不存在永久代的概念
  • 從現在看,永久代并不是一個好的概念,導緻了大量的OOM jvm參數 -XX:MaxPermSize設定永久代最大空間
  • jdk8以後廢棄永久代的概念,改用本地記憶體實作的元空間代替
  • 永久代與元空間的本質類似,都是對方法區的實作,但是元空間不再使用虛拟機設定的記憶體,而是改用本地記憶體
  • 元空間的内部結構也發生變化
  • 元空間也有可能出現OOM(超出本地記憶體大小)

5.1.4 方法區大小設定與OOM

jdk1.7

  1. -XX:PermSize 設定永久代初始記憶體 預設20.75M
  2. -XX:MaxPermSize 設定永久代最大記憶體,32位機器預設64M, 64位機器預設82M
  3. 超過 -XX:MaxPermSize 的大小 就會報OOM

jdk1.8及以後

  1. 使用 -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize替換上述兩個參數,并且上述兩個參數在jdk1.8中已廢棄
  2. 預設值與平台相關 windows下 -XX:MetaspaceSize為21M -XX:MaxMetaspaceSize無限制(-1)
  3. 與永久代不同,不指定大小的情況下,元空間會耗盡所有系統可用記憶體,如果依然發生了記憶體溢出,就會報OOM
  4. -XX:MetaspaceSize 預設21M,如果超過此水位線,就會觸發FullGC,然後解除安裝一些不用的類(類對應的類加載器不在存活),然後這個水位線就會重置,新的水位線大小取決于FullGC釋放的大小,如果釋放的不足,那麼在不超過MaxMetaspaceSize的前提下,會适當提高水位線;相反,如果釋放比較多的空間,那麼就會适當降低水位線
  5. 如果初始化的水位線設定過低,那麼在程式運作過程中可能就會觸發多次FullGC調整水位線,為了避免這種情況,可以适當的把水位線-XX:MetaspaceSize調高

package com.zy.study10;

/**

 * @Author: Zy

 * @Date: 2021/8/30 23:04

 * 測試調整jdk1.8的元空間大小

 * -XX:MetaspaceSize=50M

 * -XX:MaxMetaspaceSize=100M

 */

public class MethadAreaTest {

    public static void main(String[] args) throws InterruptedException {

        System.out.println("start");

        Thread.sleep(1000000);

    }

}

通過jps和jinfo檢視jvm參數

JVM深入學習(七)-運作時資料區之方法區

5.2 方法區的結構

方法區主要存儲類型資訊,常量,靜态變量,JIT編譯後的代碼緩存,域資訊,方法資訊等.

5.2.1 類型資訊

類,接口,枚舉,注解等類型必須存儲的資訊:

  1. 完整有效名稱(完整包名.類名)
  2. 直接父類的完整有效名稱, 接口/Object沒有父類
  3. 類型的修飾符
  4. 直接接口的一個有序清單

5.2.2 域資訊(Field)

儲存類型的所有域的資訊和域的聲明順序

域的相關資訊包括:

  1. 域名稱
  2. 域類型
  3. 域修飾符(public private protected static final volatile transient的某個子集)

5.2.3 方法資訊(Method)

儲存類型中的所有方法的資訊

方法的相關資訊包括:

  1. 方法名稱
  2. 方法的傳回值(包括void)
  3. 方法的參數和類型(按順序)
  4. 方法的修飾符(public private protected static final volatile transient的某個子集)
  5. 方法的位元組碼(Bytes),操作數棧和局部變量表及大小(abstract和native方法除外)
  6. 異常表 (abstract和native方法除外)
    1. 每個異常處理的開始位置,結束位置,代碼處理在程式計數器的中偏移位址,被捕獲的異常類在常量池中的索引

 * @Date: 2021/8/31 9:56

 * 測試方法區反編譯效果

public class MethodStructureTest {

    public int num = 0;

    private String str = "測試内部結構";

    public void test1(){

        System.out.println("num"+num);

        System.out.println(str);

    public void test2(){

        int i = 0;

        int j = 0;

        try {

            int k = i/j;

        } catch (Exception e) {

            e.printStackTrace();

        }

可以反編譯後檢視class檔案,進而看到方法區的具體結構:

Classfile /E:/張堯/idea項目/jvm/target/classes/com/zy/study10/MethodStructureTest.class

  Last modified 2021-8-31; size 1137 bytes

  MD5 checksum 0de91cc4cb8d502aa743a5e961bcbfec

  Compiled from "MethodStructureTest.java"

public class com.zy.study10.MethodStructureTest

  minor version: 0

  major version: 52

  flags: ACC_PUBLIC, ACC_SUPER

Constant pool:

   #1 = Methodref          #16.#39        // java/lang/Object."<init>":()V

   #2 = Fieldref           #15.#40        // com/zy/study10/MethodStructureTest.num:I

   #3 = String             #41            // 測試内部結構

   #4 = Fieldref           #15.#42        // com/zy/study10/MethodStructureTest.str:Ljava/lang/String;

   #5 = Fieldref           #43.#44        // java/lang/System.out:Ljava/io/PrintStream;

   #6 = Class              #45            // java/lang/StringBuilder

   #7 = Methodref          #6.#39         // java/lang/StringBuilder."<init>":()V

   #8 = String             #17            // num

   #9 = Methodref          #6.#46         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

  #10 = Methodref          #6.#47         // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;

  #11 = Methodref          #6.#48         // java/lang/StringBuilder.toString:()Ljava/lang/String;

  #12 = Methodref          #49.#50        // java/io/PrintStream.println:(Ljava/lang/String;)V

  #13 = Class              #51            // java/lang/Exception

  #14 = Methodref          #13.#52        // java/lang/Exception.printStackTrace:()V

  #15 = Class              #53            // com/zy/study10/MethodStructureTest

  #16 = Class              #54            // java/lang/Object

  #17 = Utf8               num

  #18 = Utf8               I

  #19 = Utf8               str

  #20 = Utf8               Ljava/lang/String;

  #21 = Utf8               <init>

  #22 = Utf8               ()V

  #23 = Utf8               Code

  #24 = Utf8               LineNumberTable

  #25 = Utf8               LocalVariableTable

  #26 = Utf8               this

  #27 = Utf8               Lcom/zy/study10/MethodStructureTest;

  #28 = Utf8               test1

  #29 = Utf8               test2

  #30 = Utf8               e

  #31 = Utf8               Ljava/lang/Exception;

  #32 = Utf8               i

  #33 = Utf8               j

  #34 = Utf8               StackMapTable

  #35 = Class              #53            // com/zy/study10/MethodStructureTest

  #36 = Class              #51            // java/lang/Exception

  #37 = Utf8               SourceFile

  #38 = Utf8               MethodStructureTest.java

  #39 = NameAndType        #21:#22        // "<init>":()V

  #40 = NameAndType        #17:#18        // num:I

  #41 = Utf8               測試内部結構

  #42 = NameAndType        #19:#20        // str:Ljava/lang/String;

  #43 = Class              #55            // java/lang/System

  #44 = NameAndType        #56:#57        // out:Ljava/io/PrintStream;

  #45 = Utf8               java/lang/StringBuilder

  #46 = NameAndType        #58:#59        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

  #47 = NameAndType        #58:#60        // append:(I)Ljava/lang/StringBuilder;

  #48 = NameAndType        #61:#62        // toString:()Ljava/lang/String;

  #49 = Class              #63            // java/io/PrintStream

  #50 = NameAndType        #64:#65        // println:(Ljava/lang/String;)V

  #51 = Utf8               java/lang/Exception

  #52 = NameAndType        #66:#22        // printStackTrace:()V

  #53 = Utf8               com/zy/study10/MethodStructureTest

  #54 = Utf8               java/lang/Object

  #55 = Utf8               java/lang/System

  #56 = Utf8               out

  #57 = Utf8               Ljava/io/PrintStream;

  #58 = Utf8               append

  #59 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;

  #60 = Utf8               (I)Ljava/lang/StringBuilder;

  #61 = Utf8               toString

  #62 = Utf8               ()Ljava/lang/String;

  #63 = Utf8               java/io/PrintStream

  #64 = Utf8               println

  #65 = Utf8               (Ljava/lang/String;)V

  #66 = Utf8               printStackTrace

{

  public int num;

    descriptor: I

    flags: ACC_PUBLIC

  private java.lang.String str;

    descriptor: Ljava/lang/String;

    flags: ACC_PRIVATE

  public com.zy.study10.MethodStructureTest();

    descriptor: ()V

    Code:

      stack=2, locals=1, args_size=1

         0: aload_0

         1: invokespecial #1                  // Method java/lang/Object."<init>":()V

         4: aload_0

         5: iconst_0

         6: putfield      #2                  // Field num:I

         9: aload_0

        10: ldc           #3                  // String 測試内部結構

        12: putfield      #4                  // Field str:Ljava/lang/String;

        15: return

      LineNumberTable:

        line 8: 0

        line 9: 4

        line 10: 9

      LocalVariableTable:

        Start  Length  Slot  Name   Signature

            0      16     0  this   Lcom/zy/study10/MethodStructureTest;

  public void test1();

      stack=3, locals=1, args_size=1

         0: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;

         3: new           #6                  // class java/lang/StringBuilder

         6: dup

         7: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V

        10: ldc           #8                  // String num

        12: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

        15: aload_0

        16: getfield      #2                  // Field num:I

        19: invokevirtual #10                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;

        22: invokevirtual #11                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;

        25: invokevirtual #12                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V

        28: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;

        31: aload_0

        32: getfield      #4                  // Field str:Ljava/lang/String;

        35: invokevirtual #12                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V

        38: return

        line 14: 0

        line 15: 28

        line 16: 38

            0      39     0  this   Lcom/zy/study10/MethodStructureTest;

  public void test2();

      stack=2, locals=4, args_size=1

         0: iconst_0

         1: istore_1

         2: iconst_0

         3: istore_2

         4: iload_1

         5: iload_2

         6: idiv

         7: istore_3

         8: goto          16

        11: astore_3

        12: aload_3

        13: invokevirtual #14                 // Method java/lang/Exception.printStackTrace:()V

        16: return

      Exception table:

         from    to  target type

             4     8    11   Class java/lang/Exception

        line 19: 0

        line 20: 2

        line 23: 4

        line 26: 8

        line 24: 11

        line 25: 12

        line 27: 16

           12       4     3     e   Ljava/lang/Exception;

            0      17     0  this   Lcom/zy/study10/MethodStructureTest;

            2      15     1     i   I

            4      13     2     j   I

      StackMapTable: number_of_entries = 2

        frame_type = 255 /* full_frame */

          offset_delta = 11

          locals = [ class com/zy/study10/MethodStructureTest, int, int ]

          stack = [ class java/lang/Exception ]

        frame_type = 4 /* same */

SourceFile: "MethodStructureTest.java"

5.2.4 運作時常量池

  1. 位元組碼中包含了常量池
  2. 方法中包含了運作時常量池,運作時常量池就是将位元組碼中的常量池加載到記憶體中

常量池:

  1. 常量池中的類型包括: 數量值,字元串值,類引用,字段引用,方法引用
  2. 常量池可以看作一張表,存儲了類名,方法名,字段名,參數類型等等資訊,虛拟機在執行代碼的時候根據指令找到對應的常量池索引,進而使用
  3. 常量池内部是嵌套的
    JVM深入學習(七)-運作時資料區之方法區

運作時常量池:

  1. 運作時常量池是方法區的一部分
  2. 運作時常量池是在類和接口等類型加載到jvm的時候建立的,将常量池中的字面量和符号引用存儲到運作時常量池,并将符号引用轉換為直接引用
  3. 運作時常量池具有動态性,即可以在運作時動态添加
  4. 運作時常量池是通過索引通路的,常量池的容量要比實際存儲數量大1
    JVM深入學習(七)-運作時資料區之方法區
    JVM深入學習(七)-運作時資料區之方法區
  5. 當運作時常量池超過方法區的最大記憶體時,會OOM

5.3 方法區的演變過程

從jdk6,jdk7,jdk8看方法區的變化過程

jdk6 永久代,靜态變量/常量池存放在永久代中
jdk7 永久代,逐漸去除永久代,靜态變量/常量池移到了堆中
jdk8 元空間,但是靜态變量/常量池還是在堆中

為什麼要用元空間替代永久代?

oracle官網的解釋是,JRocket/J9都使用了元空間,并且Oracle已經收購了JRocket,是以就使用了元空間.

從調優上了解:

  1. 永久代大小難以設定,如果設定過大,比較浪費虛拟機記憶體,如果設定過小,又會觸發多次FullGC,影響性能
  2. 永久代難以調優,永久代正常情況下很少GC,難以控制調優

相比之下元空間使用本地記憶體,能用多大就用多大,也不用jvm考慮GC問題,提高了性能

從上述演變過程解釋一下為什麼StringTable要移動到堆?

因為開發過程中建立大量的字元串,這些字元串如果都放到永久代中,由于永久代的GC效率不是很高,隻要當老年代/永久代空間不足時才會觸發FullGC然後回收永久代中,這樣就導緻了大量的字元串不會被回收,可能會導緻永久代空間不足,移動到堆之後,就可以通過YGC快速回收.

注意: 靜态變量存放在堆中指的是變量引用本身,而不是對象本身,建立的對象都是存放在堆中的,而引用本身則是随着jdk版本的不同存放的地方也不同,jdk6中存放在永久代,7/8存放在堆中

5.4 方法區的垃圾回收

java虛拟機規範中并沒有強制要求jvm要對方法區進行回收,針對HotSpot來說,還是有回收的:

方法區的垃圾回收主要針對:

  1. 運作時常量池中不再使用的常量
  2. 不再使用的類型資訊

5.4.1 常量的回收規則

常量主要指的是字面量和符号引用

主要包括:

  1. 類和接口的全限定名(完整包名+類名)
  2. 字段的名稱和描述符
  3. 方法的名稱和描述符

在hotspot中,隻要沒有地方引用該常量,就将該常量回收,類似對堆中對象的回收

5.4.2 類型的回收

判斷一個類是否不再使用,需要同時滿足如下關系:

  1. 該類的所有執行個體都被回收,堆中不存在該類及其派生子類的執行個體
  2. 該類的類加載器被回收
  3. 該類的Class對象沒有任何地方引用,無法通過反射通路該對象

滿足上述條件後,才可以允許該類被回收,但是并不一定回收.

jvm參數: -Xnoclassgc控制是否回收類

jvm參數: -verbose:class -XX:+TraceClass-Loading -XX:TraceClassUnloading可以檢視類的加載和解除安裝資訊.

總結:

方法區的回收主要針對常量,對于類的回收條件比較苛刻,一般情況下不會回收類資訊,特殊情況下如果使用大量的動态生成,反射的情況下,需要考慮類的回收.