文章目錄
-
- 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"