天天看點

JVM——方法區

文章目錄

    • 1. 什麼是方法區
    • 2. 永久代與元空間
    • 3. 常量池與運作時常量池
    • 4. 方法區垃圾回收
    • 5. 示例

1. 什麼是方法區

方法區,也稱非堆(Non-Heap),是一個被線程共享的記憶體區域。

其中主要存儲加載的類位元組碼、class/method/field等中繼資料對象、static-final常量、static變量、jit編譯器編譯後的代碼緩存等資料。另外,方法區包含了一個特殊的區域“運作時常量池”。

方法區包含:

  • 類資訊:修飾符(public、abstract、final)、全類名、接口清單、父類全類名
  • 域(Field,即方法中成員變量)資訊:域名稱、域類型、域修飾符:

    private int num;

  • 方法資訊:方法名稱、傳回類型、參數名稱及類型、異常資訊等:

    public void delete (String path) throws IOException{ }

  • 運作時常量池:字面值(比如字元串值)、符号引用
  • 靜态變量(static、非final):靜态變量會在類加載時,賦初始值;final變量(常量)在編譯期就被确定,放在運作時常量池中
  • 對類加載器的引用
  • 對Class類的引用
  • 方法表

2. 永久代與元空間

方法區是JVM的規範,永久代與元空間是HotSpot JVM兩種落地實作方式。 jdk1.7通過永久代實作,jdk1.8通過元空間實作

  • PermGen Space(永久代)

永久代指的是永久儲存區域,邏輯上屬于堆空間。主要存放Class和Meta(中繼資料)的資訊。Classic在被加載的時候被放入永久代。

  • MetaSpace(元空間)

元空間和永久代類似,都是對JVM規範中方法的實作。不過元空間并不在虛拟機中,而是使用本地記憶體。預設情況下,元空間的大小僅受本地記憶體的限制。類的中繼資料放入native memory,字元串池和類的靜态變量放入java堆中。

-XX:MetaspaceSize ,初始空間大小
-XX:MaxMetaspaceSize,最大空間
           
  • 采用元空間而不用永久代的原因
    • 中繼資料(比如字元串)和class對象存放在永久代中,容易出現性能問題和記憶體溢出
    • 類及方法的資訊等比較難确定其大小,是以對于永久代大小指定比較困難,大小容易出現永久代溢出,太大容易導緻老年代溢出(堆記憶體不變,此消彼長)
    • 永久代會為GC帶來不必要的複雜度,并且回收效率偏低
版本 變化
jdk1.6 有永久代,靜态變量存放在永久代上
jdk1.7 有永久代,但已逐漸“去永久代”,字元串常量池、靜态變量從永久代中移除,存放在堆中
jdk1.8 無永久代,類型資訊、字段、方法、常量儲存在本地記憶體的元空間,但字元串常量池、靜态變量儲存在堆中

3. 常量池與運作時常量池

以上述位元組碼檔案為例

public void test1() {
	int count =20;
	System.out.println("count="+count);
}
           

編譯得到位元組碼檔案

0 bipush 20
 2 istore_1
 3 getstatic #3 <java/lang/System.out>
 6 new #4 <java/lang/StringBuilder>
 9 dup
10 invokespecial #5 <java/lang/StringBuilder.<init>>
13 ldc #6 <count=>
15 invokevirtual #7 <java/lang/StringBuilder.append>
18 iload_1
19 invokevirtual #8 <java/lang/StringBuilder.append>
22 invokevirtual #9 <java/lang/StringBuilder.toString>
25 invokevirtual #10 <java/io/PrintStream.println>
28 return
           

以其中第3行為例:調用 #3 <java/lang/System.out>,檢視位元組碼檔案中的常量池

/* Fieldref:指向字段的符号引用 */
 #3 = Fieldref           #44.#45        // java/lang/System.out:Ljava/io/PrintStream;
/* Class:指向類(包括數組類、枚舉類)和接口的符号引用 */
#44 = Class              #63            // java/lang/System
/* NameAndType:給出字段或方法的名稱和描述符 */
#45 = NameAndType        #64:#65        // out:Ljava/io/PrintStream;
/* Utf8:存放MUTF-8編碼的字元串 */
#63 = Utf8               java/lang/System
#64 = Utf8               out
#65 = Utf8               Ljava/io/PrintStream;
           

第6行:new #4 <java/lang/StringBuilder>

#4 = Class              #46            // java/lang/StringBuilder
#46 = Utf8               java/lang/StringBuilder
           

第10行:invokespecial #5 <java/lang/StringBuilder.>

#5 = Methodref          #4.#42         // java/lang/StringBuilder."<init>":()V
 #4 = Class              #46            // java/lang/StringBuilder  
#42 = NameAndType        #25:#26        // "<init>":()V
#46 = Utf8               java/lang/StringBuilder
#25 = Utf8               <init>
#26 = Utf8               ()V
           

是以,常量池中至少包含:

  • 字元串值:

    #6 = String #47 // count=

  • 字段的符号引用:

    #3 = Fieldref #44.#45 // java/lang/System.out:Ljava/io/PrintStream;

  • 類的符号引用:

    #44 = Class #63 // java/lang/System

  • 方法的符号引用:

    #45 = NameAndType #64:#65

  • 等其他資訊,可參考:常量池中存儲的資料類型

什麼是運作時常量池?

  • 常量池是Class檔案中的一部分,用于存儲編譯器生成的各種字面量與符号引用
  • 常量池中内容經過Class Loader過程存放到方法區中的運作時常量池
  • 相比常量池,具有動态性

4. 方法區垃圾回收

主要回收:

  • 廢棄的常量
  • 不再使用的類型(判斷起來很麻煩)
    • 該類所有的執行個體被回收
    • 加載該類的加載器被回收
    • 該類不被引用,無法通過反射通路該類的方法

5. 示例

  • 使用位元組碼檔案分析方法區
public class TestMethodArea extends Object implements Comparable<String>,Serializable{
	//屬性
	public int num=10;
	private static String s="測試方法區内部結構";
	
	//構造器,使用預設的
	
	//自定義方法
	public void test1() {
		int count =20;
		System.out.println("count="+count);
	}
	public static int test2(int cal) {
		int res=0;
		try {
			int val=20;
			res=val/cal;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return res;
	}
	
	//繼承接口重寫的方法
	@Override
	public int compareTo(String o) {
		return 0;
	}

}
           
  • 編譯:javac TestMethodArea.java
  • 反編譯:javap -v -p TestMethodArea.class > test1.txt
  • 打開test1.txt
Classfile /C:/Users/cc/Desktop/TestMethodArea.class
  Last modified 2020-8-3; size 1338 bytes
  MD5 checksum 6deebe9989d830f376c1755cb0562380
  Compiled from "TestMethodArea.java"
//方法資訊
public class com.cc.methodarea.TestMethodArea extends java.lang.Object implements java.lang.Comparable<java.lang.String>, java.io.Serializable
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
//常量池
Constant pool:
   #1 = Methodref          #18.#42        // java/lang/Object."<init>":()V
   #2 = Fieldref           #17.#43        // com/cc/methodarea/TestMethodArea.num:I
   #3 = Fieldref           #44.#45        // java/lang/System.out:Ljava/io/PrintStream;
   #4 = Class              #46            // java/lang/StringBuilder
   #5 = Methodref          #4.#42         // java/lang/StringBuilder."<init>":()V
   #6 = String             #47            // count=
   #7 = Methodref          #4.#48         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #8 = Methodref          #4.#49         // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
   #9 = Methodref          #4.#50         // java/lang/StringBuilder.toString:()Ljava/lang/String;
  #10 = Methodref          #51.#52        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #11 = Class              #53            // java/lang/Exception
  #12 = Methodref          #11.#54        // java/lang/Exception.printStackTrace:()V
  #13 = Class              #55            // java/lang/String
  #14 = Methodref          #17.#56        // com/cc/methodarea/TestMethodArea.compareTo:(Ljava/lang/String;)I
  #15 = String             #57            // 測試方法區内部結構
  #16 = Fieldref           #17.#58        // com/cc/methodarea/TestMethodArea.s:Ljava/lang/String;
  #17 = Class              #59            // com/cc/methodarea/TestMethodArea
  #18 = Class              #60            // java/lang/Object
  #19 = Class              #61            // java/lang/Comparable
  #20 = Class              #62            // java/io/Serializable
  #21 = Utf8               num
  #22 = Utf8               I
  #23 = Utf8               s
  #24 = Utf8               Ljava/lang/String;
  #25 = Utf8               <init>
  #26 = Utf8               ()V
  #27 = Utf8               Code
  #28 = Utf8               LineNumberTable
  #29 = Utf8               test1
  #30 = Utf8               test2
  #31 = Utf8               (I)I
  #32 = Utf8               StackMapTable
  #33 = Class              #53            // java/lang/Exception
  #34 = Utf8               compareTo
  #35 = Utf8               (Ljava/lang/String;)I
  #36 = Utf8               (Ljava/lang/Object;)I
  #37 = Utf8               <clinit>
  #38 = Utf8               Signature
  #39 = Utf8               Ljava/lang/Object;Ljava/lang/Comparable<Ljava/lang/String;>;Ljava/io/Serializable;
  #40 = Utf8               SourceFile
  #41 = Utf8               TestMethodArea.java
  #42 = NameAndType        #25:#26        // "<init>":()V
  #43 = NameAndType        #21:#22        // num:I
  #44 = Class              #63            // java/lang/System
  #45 = NameAndType        #64:#65        // out:Ljava/io/PrintStream;
  #46 = Utf8               java/lang/StringBuilder
  #47 = Utf8               count=
  #48 = NameAndType        #66:#67        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #49 = NameAndType        #66:#68        // append:(I)Ljava/lang/StringBuilder;
  #50 = NameAndType        #69:#70        // toString:()Ljava/lang/String;
  #51 = Class              #71            // java/io/PrintStream
  #52 = NameAndType        #72:#73        // println:(Ljava/lang/String;)V
  #53 = Utf8               java/lang/Exception
  #54 = NameAndType        #74:#26        // printStackTrace:()V
  #55 = Utf8               java/lang/String
  #56 = NameAndType        #34:#35        // compareTo:(Ljava/lang/String;)I
  #57 = Utf8               測試方法區内部結構
  #58 = NameAndType        #23:#24        // s:Ljava/lang/String;
  #59 = Utf8               com/cc/methodarea/TestMethodArea
  #60 = Utf8               java/lang/Object
  #61 = Utf8               java/lang/Comparable
  #62 = Utf8               java/io/Serializable
  #63 = Utf8               java/lang/System
  #64 = Utf8               out
  #65 = Utf8               Ljava/io/PrintStream;
  #66 = Utf8               append
  #67 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #68 = Utf8               (I)Ljava/lang/StringBuilder;
  #69 = Utf8               toString
  #70 = Utf8               ()Ljava/lang/String;
  #71 = Utf8               java/io/PrintStream
  #72 = Utf8               println
  #73 = Utf8               (Ljava/lang/String;)V
  #74 = Utf8               printStackTrace
{
  //域資訊
  public int num;
    descriptor: I
    flags: ACC_PUBLIC

  private static java.lang.String s;
    descriptor: Ljava/lang/String;
    flags: ACC_PRIVATE, ACC_STATIC

  //預設構造器 init方法
  public com.cc.methodarea.TestMethodArea();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: bipush        10
         7: putfield      #2                  // Field num:I
        10: return
      LineNumberTable:
        line 5: 0
        line 7: 4

  //方法資訊
  public void test1();
    descriptor: ()V
    flags: ACC_PUBLIC
    //位元組碼
    Code:
      //操作數棧、局部變量表、參數大小(指this對象)
      stack=3, locals=2, args_size=1
         0: bipush        20
         2: istore_1
         3: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         6: new           #4                  // class java/lang/StringBuilder
         9: dup
        10: invokespecial #5                  // Method java/lang/StringBuilder."<init>":()V
        13: ldc           #6                  // String count=
        15: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        18: iload_1
        19: invokevirtual #8                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
        22: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        25: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        28: return
      LineNumberTable:
        line 14: 0
        line 15: 3
        line 16: 28

  public static int test2(int);
    descriptor: (I)I
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1(指int形參、static沒有this類型)
         0: iconst_0
         1: istore_1
         2: bipush        20
         4: istore_2
         5: iload_2
         6: iload_0
         7: idiv
         8: istore_1
         9: goto          17
        12: astore_2
        13: aload_2
        14: invokevirtual #12                 // Method java/lang/Exception.printStackTrace:()V
        17: iload_1
        18: ireturn
      //異常表
      Exception table:
         from    to  target type
             2     9    12   Class java/lang/Exception
      LineNumberTable:
        line 18: 0
        line 20: 2
        line 21: 5
        line 24: 9
        line 22: 12
        line 23: 13
        line 25: 17
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 12
          locals = [ int, int ]
          stack = [ class java/lang/Exception ]
        frame_type = 4 /* same */

  public int compareTo(java.lang.String);
    descriptor: (Ljava/lang/String;)I
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=2, args_size=2
         0: iconst_0
         1: ireturn
      LineNumberTable:
        line 31: 0

  public int compareTo(java.lang.Object);
    descriptor: (Ljava/lang/Object;)I
    flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: checkcast     #13                 // class java/lang/String
         5: invokevirtual #14                 // Method compareTo:(Ljava/lang/String;)I
         8: ireturn
      LineNumberTable:
        line 5: 0

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: ldc           #15                 // String 測試方法區内部結構
         2: putstatic     #16                 // Field s:Ljava/lang/String;
         5: return
      LineNumberTable:
        line 8: 0
}
Signature: #39                          // Ljava/lang/Object;Ljava/lang/Comparable<Ljava/lang/String;>;Ljava/io/Serializable;
SourceFile: "TestMethodArea.java"