1. 棧、堆、方法區(qū)的交互關(guān)系
從線程共享與否的角度來(lái)看
交互關(guān)系
2. 方法區(qū)的理解
2.1 官方文檔
The Java Virtual Machine has a method area that is shared among all Java Virtual Machine threads. The method area is analogous to the storage area for compiled code of a conventional language or analogous to the "text" segment in an operating system process. It stores per-class structures such as the run-time constant pool, field and method data, and the code for methods and constructors, including the special methods (§2.9) used in class and instance initialization and interface initialization.
The method area is created on virtual machine start-up. Although the method area is logically part of the heap, simple implementations may choose not to either garbage collect or compact it. This specification does not mandate the location of the method area or the policies used to manage compiled code. The method area may be of a fixed size or may be expanded as required by the computation and may be contracted if a larger method area becomes unnecessary. The memory for the method area does not need to be contiguous.
A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of the method area, as well as, in the case of a varying-size method area, control over the maximum and minimum method area size.
The following exceptional condition is associated with the method area:
If memory in the method area cannot be made available to satisfy an allocation request, the Java Virtual Machine throws an OutOfMemoryError.
參考
2.2 方法區(qū)在哪里
《Java虛擬機(jī)規(guī)范》中明確說(shuō)明:”盡管所有的方法區(qū)在邏輯上是屬于堆的一部分,但一些簡(jiǎn)單的實(shí)現(xiàn)可能不會(huì)選擇去進(jìn)行垃圾收集或者進(jìn)行壓縮?!暗珜?duì)于HotSpot JVM而言,方法區(qū)還有一個(gè)別名叫做Non-Heap(非堆),目的就是要和堆分開(kāi)
所以,方法區(qū)看做是一塊獨(dú)立于Java堆的內(nèi)存空間
2.3 方法區(qū)的基本理解
- 方法區(qū)和Java堆一樣,是各個(gè)線程共享的內(nèi)存區(qū)域
- 方法區(qū)在JVM啟動(dòng)的時(shí)候被創(chuàng)建,并且它的實(shí)際的物理內(nèi)存空間中和Java堆區(qū)一樣都可以是不連續(xù)的
- 方法區(qū)的大小,跟堆空間一樣,可以選擇固定大小或者可擴(kuò)展
- 方法區(qū)的大小決定了系統(tǒng)可以保存多少個(gè)類(lèi),如果系統(tǒng)定義了太多的類(lèi),導(dǎo)致方法區(qū)溢出,虛擬機(jī)同樣會(huì)拋出內(nèi)存溢出錯(cuò)誤:java.lang.OurOfMemoryError: PermGen space或者java.lang.OurOfMemoryError: Metaspace
- 加載大量的第三方j(luò)ar包
- Tomcat部署的工程過(guò)多(30-50)
- 大量動(dòng)態(tài)生成反射類(lèi)
代碼測(cè)試:
public class MethodAreaTest {
public static void main(String[] args) {
Sy("testing");
try {
T(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 執(zhí)行后使用jvisualvm查看類(lèi)加載數(shù)量:
?可以看到加載了1530個(gè)類(lèi)而且數(shù)量還在動(dòng)態(tài)變化中
- 關(guān)閉JVM就會(huì)釋放這個(gè)區(qū)域的內(nèi)存
2.4 HotSpot中方法區(qū)的演進(jìn)
- 在JDK7及以前,習(xí)慣上把方法區(qū)稱(chēng)為永久代。JDK8開(kāi)始,使用元空間取代了永久代
In JDK8, classes metadata is now stored in the native heap and this space is called Metaspace.
- 在本質(zhì)上,方法區(qū)和永久代并不等價(jià)。僅是對(duì)HotSpot虛擬機(jī)而言的?!禞ava虛擬機(jī)規(guī)范》對(duì)如何實(shí)現(xiàn)方法區(qū),不做統(tǒng)一要求。例如:BEA JRockit / IBM J9中不存在永久代的概念?,F(xiàn)在看來(lái),當(dāng)年使用永久代,并不是好的選擇。導(dǎo)致Java程序更容易OOM (超過(guò)-XX:MaxPermSize上限)
- 而到了JDK8, 終于完全廢棄了永久代的概念,改用與JRockit、J9一樣在本地內(nèi)存中實(shí)現(xiàn)的元空間來(lái)代替
- 元空間的本質(zhì)和永久代類(lèi)似,都是對(duì)JVM規(guī)范中方法區(qū)的實(shí)現(xiàn)。不過(guò)元空間與永久代最大的區(qū)別在于:元空間不在虛擬機(jī)設(shè)置的內(nèi)存中,而是使用本地內(nèi)存
- 永久代、元空間二者并不只是名字變了,內(nèi)部結(jié)構(gòu)也調(diào)整了
- 根據(jù)《Java虛擬機(jī)規(guī)范》規(guī)定,如果方法區(qū)無(wú)法滿足新的內(nèi)存分配需求時(shí),將拋出OOM異常java.lang.OurOfMemoryError: Metaspace
3. 設(shè)置方法區(qū)大小與OOM
- 方法區(qū)的大小不必是固定的,JVM可以根據(jù)應(yīng)用的需要?jiǎng)討B(tài)調(diào)整
- JDK7及以前:
- 通過(guò)-XX:PermSize來(lái)設(shè)置永久代初始分配空間,默認(rèn)值是20.75M;
- 通過(guò)-XX:MaxPermSize來(lái)設(shè)置永久代最大可分配空間,32位機(jī)器默認(rèn)是64M,64位機(jī)器默認(rèn)是82M。
- 當(dāng)JVM加載的類(lèi)信息容量超過(guò)了這個(gè)值,會(huì)報(bào)異常java.lang.OurOfMemoryError: PermGen space
- JDK8及以后:
- 元數(shù)據(jù)區(qū)大小可以使用參數(shù)-XX:MetaspaceSize和-XX:MaxMetaspaceSize指定,替代上述原有的兩個(gè)參數(shù)
-XX:MaxPermSize=size Sets the maximum permanent generation space size (in bytes). This option was deprecated in JDK 8, and superseded by the -XX:MaxMetaspaceSize o Sets the space (in bytes) allocated to the permanent generation that triggers a garbage collection if it is exceeded. This option was deprecated un JDK 8, and superseded by the -XX:MetaspaceSize option.
- 默認(rèn)值依賴(lài)于平臺(tái)。windows下,-XX:MetaspaceSize是21M,-XX:MaxMetaspaceSize的值是-1,即沒(méi)有限制
- 與永久代不同,如果不指定大小,默認(rèn)情況下,虛擬機(jī)會(huì)耗盡所有可用系統(tǒng)內(nèi)存。如果元數(shù)據(jù)區(qū)發(fā)生溢出,虛擬機(jī)一樣會(huì)拋出異常java.lang.OurOfMemoryError: Metaspace
- -XX:MetaspaceSize設(shè)置初始的元空間大小。對(duì)于一個(gè)64位的服務(wù)器端JVM來(lái)說(shuō),其默認(rèn)的-XX:MetaspaceSize值為21M,這就是初始的高水位線,一旦觸及這個(gè)水位線,F(xiàn)ull GC將會(huì)被觸發(fā)并卸載沒(méi)用的類(lèi)(即這些類(lèi)對(duì)應(yīng)的類(lèi)加載器不再存活),然后這個(gè)高水位線將會(huì)重置。新的高水位線的值取決于GC后釋放了多少元空間。如果釋放的空間不足,那么在不超過(guò)MaxMetaspaceSize時(shí),適當(dāng)提高該值。如果釋放空間過(guò)多,則適當(dāng)降低該值
- 如果初始化的高水位線設(shè)置過(guò)低,上述高水位線調(diào)整情況會(huì)發(fā)生很多次。通過(guò)垃圾回收器的日志可以觀察到Full GC多次調(diào)用。為了避免頻繁地GC,建議將-XX:MetaspaceSize設(shè)置為一個(gè)相對(duì)較高的值
JDK8 環(huán)境下代碼測(cè)試
package com.na;
public class MethodAreaTest {
public static void main(String[] args) {
Sy("testing");
try {
T(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
設(shè)置運(yùn)行參數(shù)-XX:MetaspaceSize=100m -XX:MaxMetaspaceSize=100m執(zhí)行后,命令行查看如下:
$ jps
97123 Launcher
25059
97124 MethodAreaTest
97126 Jps
$ jinfo -flag MetaspaceSize 97124
-XX:MetaspaceSize=104857600
$ jinfo -flag MaxMetaspaceSize 97124
-XX:MaxMetaspaceSize=104857600
104857600/1024/1024 = 100m即為我們?cè)O(shè)置的運(yùn)行參數(shù)。 而如果在JDK8 環(huán)境下設(shè)置運(yùn)行參數(shù)-XX:PermSize=100m -XX:MaxPermSize=100m,則會(huì)輸出如下提示信息:
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=100m; support was removed in 8.0
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=100m; support was removed in 8.0
方法區(qū)OOM代碼測(cè)試
package com.na;
import jdk.in;
import jdk.in;
public class MethodAreaOOMTest extends ClassLoader {
public static void main(String[] args) {
int j = 0;
try {
MethodAreaOOMTest test = new MethodAreaOOMTest();
for (int i = 0; i < 10000; i++) {
// 創(chuàng)建ClassWriter對(duì)象,用于生成類(lèi)的二進(jìn)制字節(jié)碼
ClassWriter classWriter = new ClassWriter(0);
// 指明版本號(hào),修飾符,類(lèi)名,包名,父類(lèi),接口
cla, O, "Class" + i, null, "java/lang/Object", null);
// 返回byte[]
byte[] code = cla();
// 類(lèi)的加載
("Class" + i, code, 0, code.length); // Class對(duì)象
j++;
}
} finally {
Sy(j);
}
}
}
運(yùn)行參數(shù):-XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m, 執(zhí)行后輸出如下:
8531
Exception in thread "main" java.lang.OutOfMemoryError: Metaspace
at java.lang.Cla(Native Method)
at java.lang.Cla(ClassLoader.java:763)
at java.lang.Cla(ClassLoader.java:642)
at com.na.MethodAreaOOMTest.main(MethodAreaOOMTest.java:20)
如何解決OOM ?
要解決OOM異?;蛘遠(yuǎn)eap space的異常,一般的手段是首先通過(guò)內(nèi)存映像分析工具(如Eclipse Memory Analyzer)對(duì)dump出來(lái)的堆轉(zhuǎn)儲(chǔ)快照進(jìn)行分析,重點(diǎn)是確認(rèn)內(nèi)存中的對(duì)象是否是必要的,也就是要先分清楚到底是出現(xiàn)了內(nèi)存泄漏(Memory Leak)還是內(nèi)存溢出(Memory Overflow)
- 如果是內(nèi)存泄漏,可以進(jìn)一步通過(guò)工具查看泄露對(duì)象到GC Roots的引用鏈。于是就能找到泄露對(duì)象時(shí)通過(guò)怎樣的路徑與GC Roots相關(guān)聯(lián)并導(dǎo)致垃圾收集器無(wú)法自動(dòng)回收它們的。掌握了泄露對(duì)象的類(lèi)型信息,以及GC Roots引用鏈的信息,就可以比較準(zhǔn)確地定位出泄露代碼的位置
- 如果不存在內(nèi)存泄漏,換句話說(shuō)就是內(nèi)存中的對(duì)象確實(shí)都還必須存活著,那就應(yīng)當(dāng)檢查虛擬機(jī)的堆參數(shù)(-Xmx與-Xms),與機(jī)器物理內(nèi)存對(duì)比看是否還可以調(diào)大,從代碼上檢查是否存在某些對(duì)象生命周期過(guò)長(zhǎng)、持有狀態(tài)時(shí)間過(guò)長(zhǎng)的情況,嘗試減少程序運(yùn)行期的內(nèi)存消耗
方法區(qū)的內(nèi)部結(jié)構(gòu)
4.1 方法區(qū)中存儲(chǔ)什么
《深入理解Java虛擬機(jī)》一書(shū)中對(duì)方法區(qū)存儲(chǔ)內(nèi)容描述如下:它用于存儲(chǔ)已被虛擬機(jī)加載的類(lèi)型信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼緩存等
- 類(lèi)型信息。對(duì)每個(gè)加載的類(lèi)型(類(lèi)class,接口interface,枚舉enum,注解annotation),JVM必須在方法區(qū)中存儲(chǔ)以下類(lèi)型信息:
- 這個(gè)類(lèi)型的完整有效名稱(chēng)(全名=包名.類(lèi)名)
- 這個(gè)類(lèi)型直接父類(lèi)的完整有效名(對(duì)于interface或者是java.lang.Object,都沒(méi)有父類(lèi))
- 這個(gè)類(lèi)型的修飾符(public, abstract, final的某個(gè)子集)
- 這個(gè)類(lèi)型直接接口的一個(gè)有序列表
- 域(Field)信息
- JVM必須在方法區(qū)中保存類(lèi)型的所有域相關(guān)信息以及域的聲明順序
- 域的相關(guān)信息包括:域名稱(chēng)、域類(lèi)型、域修飾符(public, private, protected, static, final, volatile, transient的某個(gè)子集)
- 方法(Method)信息。JVM必須保存所有方法的以下信息,同域信息一樣包括聲明順序:
- 方法名稱(chēng)
- 方法的返回類(lèi)型(或void)
- 方法參數(shù)的數(shù)量和類(lèi)型(按順序)
- 方法的修飾符(public, private, protected, static, final, synchronized, native, abstract的一個(gè)子集)
- 方法的字節(jié)碼(bytecodes)、操作數(shù)棧、局部變量表及大小(abstract和native方法除外)
- 異常表(abstract和native方法除外)
- 每個(gè)異常處理的開(kāi)始位置、結(jié)束位置、代碼處理在程序計(jì)數(shù)器中的偏移地址、被捕獲的異常類(lèi)的常量池索引
代碼測(cè)試 1
package com.na;
import java.io.Serializable;
public class MethodInnerStructTest extends Object implements Comparable<String>, Serializable {
public int num = 10;
private static String str = "nasuf";
public void test() {
int count = 20;
Sy("count = " + count);
}
public static int test2(int cal) {
int result = 0;
try {
int value = 30;
result = value / cal;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
@Override
public int compareTo(String o) {
return 0;
}
}
使用javap -v -p MethodInnerStructTest反編譯后輸出如下:
Classfile /Users/nasuf/Project/JvmNotesCode/out/production/JvmNotesCode/com/nasuf/jvm
Last modified 2021-5-27; size 1600 bytes
MD5 checksum 884f65d3fee6a4e142249081ec516ffc
Compiled from "Me;
// 筆者注:類(lèi)型信息
public class com.na.MethodInnerStructTest 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.#52 // java/lang/Object."<init>":()V
#2 = Fieldref #17.#53 // com/nasuf/jvm
#3 = Fieldref #54.#55 // java/lang;
#4 = Class #56 // java/lang/StringBuilder
#5 = Methodref #4.#52 // java/lang/StringBuilder."<init>":()V
#6 = String #57 // count =
#7 = Methodref #4.#58 // java/lang:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#8 = Methodref #4.#59 // java/lang:(I)Ljava/lang/StringBuilder;
#9 = Methodref #4.#60 // java/lang:()Ljava/lang/String;
#10 = Methodref #61.#62 // java/io:(Ljava/lang/String;)V
#11 = Class #63 // java/lang/Exception
#12 = Methodref #11.#64 // java/lang:()V
#13 = Class #65 // java/lang/String
#14 = Methodref #17.#66 // com/nasuf/jvm:(Ljava/lang/String;)I
#15 = String #67 // nasuf
#16 = Fieldref #17.#68 // com/nasuf/jvm;
#17 = Class #69 // com/nasuf/jvm/MethodInnerStructTest
#18 = Class #70 // java/lang/Object
#19 = Class #71 // java/lang/Comparable
#20 = Class #72 // java/io/Serializable
#21 = Utf8 num
#22 = Utf8 I
#23 = Utf8 str
#24 = Utf8 Ljava/lang/String;
#25 = Utf8 <init>
#26 = Utf8 ()V
#27 = Utf8 Code
#28 = Utf8 LineNumberTable
#29 = Utf8 LocalVariableTable
#30 = Utf8 this
#31 = Utf8 Lcom/nasuf/jvm/MethodInnerStructTest;
#32 = Utf8 test
#33 = Utf8 count
#34 = Utf8 test2
#35 = Utf8 (I)I
#36 = Utf8 value
#37 = Utf8 e
#38 = Utf8 Ljava/lang/Exception;
#39 = Utf8 cal
#40 = Utf8 result
#41 = Utf8 StackMapTable
#42 = Class #63 // java/lang/Exception
#43 = Utf8 compareTo
#44 = Utf8 (Ljava/lang/String;)I
#45 = Utf8 o
#46 = Utf8 (Ljava/lang/Object;)I
#47 = Utf8 <clinit>
#48 = Utf8 Signature
#49 = Utf8 Ljava/lang/Object;Ljava/lang/Comparable<Ljava/lang/String;>;Ljava/io/Serializable;
#50 = Utf8 SourceFile
#51 = Utf8 Me
#52 = NameAndType #25:#26 // "<init>":()V
#53 = NameAndType #21:#22 // num:I
#54 = Class #73 // java/lang/System
#55 = NameAndType #74:#75 // out:Ljava/io/PrintStream;
#56 = Utf8 java/lang/StringBuilder
#57 = Utf8 count =
#58 = NameAndType #76:#77 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#59 = NameAndType #76:#78 // append:(I)Ljava/lang/StringBuilder;
#60 = NameAndType #79:#80 // toString:()Ljava/lang/String;
#61 = Class #81 // java/io/PrintStream
#62 = NameAndType #82:#83 // println:(Ljava/lang/String;)V
#63 = Utf8 java/lang/Exception
#64 = NameAndType #84:#26 // printStackTrace:()V
#65 = Utf8 java/lang/String
#66 = NameAndType #43:#44 // compareTo:(Ljava/lang/String;)I
#67 = Utf8 nasuf
#68 = NameAndType #23:#24 // str:Ljava/lang/String;
#69 = Utf8 com/nasuf/jvm/MethodInnerStructTest
#70 = Utf8 java/lang/Object
#71 = Utf8 java/lang/Comparable
#72 = Utf8 java/io/Serializable
#73 = Utf8 java/lang/System
#74 = Utf8 out
#75 = Utf8 Ljava/io/PrintStream;
#76 = Utf8 append
#77 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#78 = Utf8 (I)Ljava/lang/StringBuilder;
#79 = Utf8 toString
#80 = Utf8 ()Ljava/lang/String;
#81 = Utf8 java/io/PrintStream
#82 = Utf8 println
#83 = Utf8 (Ljava/lang/String;)V
#84 = Utf8 printStackTrace
{
// 筆者注:域信息
public int num;
descriptor: I
flags: ACC_PUBLIC
private static java.lang.String str;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE, ACC_STATIC
// 筆者注:方法信息
// 默認(rèn)提供無(wú)參構(gòu)造器,即使類(lèi)中沒(méi)有寫(xiě)出
// 構(gòu)造器也劃歸為方法
public com.na.MethodInnerStructTest();
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 6: 4
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/nasuf/jvm/MethodInnerStructTest;
public void test();
descriptor: ()V // 筆者注:V表示void返回值
flags: ACC_PUBLIC
Code:
stack=3, locals=2, args_size=1 // 筆者注:操作數(shù)棧深度3,局部變量表長(zhǎng)度2,參數(shù)大小1(this引用)
0: bipush 20
2: istore_1
3: getstatic #3 // Field java/lang;
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:(Ljava/lang/String;)Ljava/lang/StringBuilder;
18: iload_1
19: invokevirtual #8 // Method java/lang:(I)Ljava/lang/StringBuilder;
22: invokevirtual #9 // Method java/lang:()Ljava/lang/String;
25: invokevirtual #10 // Method java/io:(Ljava/lang/String;)V
28: return
LineNumberTable:
line 10: 0
line 11: 3
line 12: 28
LocalVariableTable:
Start Length Slot Name Signature
0 29 0 this Lcom/nasuf/jvm/MethodInnerStructTest;
3 26 1 count I
public static int test2(int);
descriptor: (I)I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1 // 筆者注:相對(duì)于實(shí)例方法,靜態(tài)方法沒(méi)有this引用,此處參數(shù)大小1表示形參
0: iconst_0
1: istore_1
2: bipush 30
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:()V
17: iload_1
18: ireturn
Exception table: // 筆者注:異常表,字節(jié)碼區(qū)域從2到9,對(duì)應(yīng)代碼區(qū)域從line 17 到 line 21
from to target type
2 9 12 Class java/lang/Exception
LineNumberTable:
line 15: 0
line 17: 2
line 18: 5
line 21: 9
line 19: 12
line 20: 13
line 22: 17
LocalVariableTable:
Start Length Slot Name Signature
5 4 2 value I
13 4 2 e Ljava/lang/Exception;
0 19 0 cal I
2 17 1 result I
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);
descriptor: (Ljava/lang/String;)I
flags: ACC_PUBLIC
Code:
stack=1, locals=2, args_size=2
0: iconst_0
1: ireturn
LineNumberTable:
line 27: 0
LocalVariableTable:
Start Length Slot Name Signature
0 2 0 this Lcom/nasuf/jvm/MethodInnerStructTest;
0 2 1 o Ljava/lang/String;
public int compareTo);
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
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Lcom/nasuf/jvm/MethodInnerStructTest;
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: ldc #15 // String nasuf
2: putstatic #16 // Field str:Ljava/lang/String;
5: return
LineNumberTable:
line 7: 0
}
Signature: #49 // Ljava/lang/Object;Ljava/lang/Comparable<Ljava/lang/String;>;Ljava/io/Serializable;
SourceFile: "Me;
non-final的類(lèi)變量
- 靜態(tài)變量和類(lèi)關(guān)聯(lián)在一起,隨著類(lèi)的加載而加載,他們成為類(lèi)數(shù)據(jù)在邏輯上的一部分
- 類(lèi)變量被類(lèi)的所有實(shí)例共享,即使沒(méi)有類(lèi)實(shí)例時(shí),也可以訪問(wèn)它
- 全局常量: static final
- 被聲明為final的類(lèi)變量的處理方法則不同,每個(gè)全局常量在編譯的時(shí)候就會(huì)被分配了
代碼測(cè)試 2
class Order {
public int i = 10;
public static int count = 1;
public static final int number = 2;
public static void hello() {
Sy("hello");
}
}
同樣使用javap -v -p Order.class反編譯后查看部分字節(jié)碼輸入如下:
public int i;
descriptor: I
flags: ACC_PUBLIC
public static int count;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC
public static final int number;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: int 2 // 筆者注:可以看到final的常量在編譯器就被賦值
4.2 運(yùn)行時(shí)常量池 vs 常量池
- 方法區(qū),內(nèi)部包含了運(yùn)行時(shí)常量池
- 字節(jié)碼文件,內(nèi)部包含了常量池
- 要弄清楚方法區(qū),需要理解清楚ClassFile,因?yàn)榧虞d類(lèi)的信息都在方法區(qū)
- 要弄清楚方法區(qū)的運(yùn)行時(shí)常量池,需要理解清楚ClassFile中的常量池。字節(jié)碼文件中的常量池,通過(guò)類(lèi)加載器加載到方法區(qū)后,成為運(yùn)行時(shí)常量池。
參考官網(wǎng)鏈接
4.2.1 常量池
字節(jié)碼文件結(jié)構(gòu):
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
一個(gè)有效的字節(jié)碼文件中,除了包含類(lèi)的版本信息、字段、方法以及接口等描述信息外,還包含一項(xiàng)信息那就是常量池表(Constant Pool Table),常量池內(nèi)存儲(chǔ)的數(shù)據(jù)類(lèi)型包括:
- 數(shù)量值
- 字符串值
- 類(lèi)引用
- 字段引用
- 方法引用
為什么需要常量池
一個(gè)Java源文件中的類(lèi)、接口,編譯后產(chǎn)生一個(gè)字節(jié)碼文件。而Java中的字節(jié)碼需要數(shù)據(jù)支持,通常這種數(shù)據(jù)會(huì)很大以至于不能直接存到字節(jié)碼里,換另一種方式,可以存到常量池。這個(gè)字節(jié)碼包含了指向常量池的引用。在動(dòng)態(tài)鏈接的時(shí)候會(huì)用到運(yùn)行時(shí)常量池。例如4.1節(jié)中的代碼測(cè)試1,使用jclasslib查看其常量池信息(反編譯字節(jié)碼也可以看到常量池信息)
而方法的字節(jié)碼指令中,#3,#4表示的就是常量池的索引
常量池可以看做是一張表,虛擬機(jī)指令根據(jù)這張常量表找到要執(zhí)行的類(lèi)名、方法名、參數(shù)類(lèi)型、字面量等類(lèi)型
4.2.2 運(yùn)行時(shí)常量池
- 運(yùn)行時(shí)常量池(Runtime Constant Pool)是方法區(qū)的一部分
- 常量池表(Constant Pool Table)是Class文件的一部分,用于存放編譯期生成的各種字面量與符號(hào)引用,這部分內(nèi)容將在類(lèi)加載后存放到方法區(qū)的運(yùn)行時(shí)常量池中
- 運(yùn)行時(shí)常量池,在加載類(lèi)和接口到虛擬機(jī)后,就會(huì)創(chuàng)建對(duì)應(yīng)的運(yùn)行時(shí)常量池
- JVM為每個(gè)已加載的類(lèi)型(類(lèi)或接口)都維護(hù)一個(gè)常量池。池中的數(shù)據(jù)項(xiàng)像數(shù)組項(xiàng)一樣,是通過(guò)索引訪問(wèn)的
- 運(yùn)行時(shí)常量池中包含多種不同的常量,包括編譯期就已經(jīng)明確的數(shù)值字面量,也包括到運(yùn)行期解析后才能夠獲得的方法或者字段引用。此時(shí)不再是常量池中的符號(hào)地址了,這里換為真實(shí)地址
- 運(yùn)行時(shí)常量池,相對(duì)于Class文件常量池的另一重要特征是:具備動(dòng)態(tài)性
- 運(yùn)行時(shí)常量池類(lèi)似于傳統(tǒng)編程語(yǔ)言中的符號(hào)表(symbol table),但是它所包含的數(shù)據(jù)卻比符號(hào)表要更加豐富一些
- 當(dāng)創(chuàng)建類(lèi)或接口的運(yùn)行時(shí)常量池時(shí),如果構(gòu)造運(yùn)行時(shí)常量池所需的內(nèi)存空間超過(guò)了方法區(qū)所能提供的最大值,則JVM會(huì)拋出OutOfMemoryError異常
5. 方法區(qū)使用舉例
代碼測(cè)試
package com.na;
public class MethodAreaDemo {
public static void main(String[] args) {
int x = 500;
int y = 100;
int a = x / y;
int b = 50;
Sy(a + b);
}
}
反編譯字節(jié)碼如下:
Classfile /Users/nasuf/Project/JvmNotesCode/out/production/JvmNotesCode/com/nasuf/jvm
Last modified 2021-5-28; size 632 bytes
MD5 checksum 098f118c12068e432489305b444363e3
Compiled from "Me;
public class com.na.MethodAreaDemo
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #5.#24 // java/lang/Object."<init>":()V
#2 = Fieldref #25.#26 // java/lang;
#3 = Methodref #27.#28 // java/io:(I)V
#4 = Class #29 // com/nasuf/jvm/MethodAreaDemo
#5 = Class #30 // java/lang/Object
#6 = Utf8 <init>
#7 = Utf8 ()V
#8 = Utf8 Code
#9 = Utf8 LineNumberTable
#10 = Utf8 LocalVariableTable
#11 = Utf8 this
#12 = Utf8 Lcom/nasuf/jvm/MethodAreaDemo;
#13 = Utf8 main
#14 = Utf8 ([Ljava/lang/String;)V
#15 = Utf8 args
#16 = Utf8 [Ljava/lang/String;
#17 = Utf8 x
#18 = Utf8 I
#19 = Utf8 y
#20 = Utf8 a
#21 = Utf8 b
#22 = Utf8 SourceFile
#23 = Utf8 Me
#24 = NameAndType #6:#7 // "<init>":()V
#25 = Class #31 // java/lang/System
#26 = NameAndType #32:#33 // out:Ljava/io/PrintStream;
#27 = Class #34 // java/io/PrintStream
#28 = NameAndType #35:#36 // println:(I)V
#29 = Utf8 com/nasuf/jvm/MethodAreaDemo
#30 = Utf8 java/lang/Object
#31 = Utf8 java/lang/System
#32 = Utf8 out
#33 = Utf8 Ljava/io/PrintStream;
#34 = Utf8 java/io/PrintStream
#35 = Utf8 println
#36 = Utf8 (I)V
{
public com.na.MethodAreaDemo();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/nasuf/jvm/MethodAreaDemo;
public static void main[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=5, args_size=1
0: sipush 500
3: istore_1
4: bipush 100
6: istore_2
7: iload_1
8: iload_2
9: idiv
10: istore_3
11: bipush 50
13: istore 4
15: getstatic #2 // Field java/lang;
18: iload_3
19: iload 4
21: iadd
22: invokevirtual #3 // Method java/io:(I)V
25: return
LineNumberTable:
line 5: 0
line 6: 4
line 7: 7
line 8: 11
line 9: 15
line 10: 25
LocalVariableTable:
Start Length Slot Name Signature
0 26 0 args [Ljava/lang/String;
4 22 1 x I
7 19 2 y I
11 15 3 a I
15 11 4 b I
}
SourceFile: "Me;
字節(jié)碼解析
#2 = Fieldref #25.#26 // java/lang;
...
#25 = Class #31 // java/lang/System
#26 = NameAndType #32:#33 // out:Ljava/io/PrintStream;
...
#31 = Utf8 java/lang/System
#32 = Utf8 out
#33 = Utf8 Ljava/io/PrintStream;
···
#3 = Methodref #27.#28 // java/io:(I)V
...
#27 = Class #34 // java/io/PrintStream
#28 = NameAndType #35:#36 // println:(I)V
...
#34 = Utf8 java/io/PrintStream
#35 = Utf8 println
#36 = Utf8 (I)V
6. 方法區(qū)的演進(jìn)細(xì)節(jié)
- 首先明確,只有HotSpot虛擬機(jī)才有永久代。BEA JRockit、IBM J9等,是不存在永久代的概念的。原則上如何實(shí)現(xiàn)方法區(qū)屬于虛擬機(jī)實(shí)現(xiàn)細(xì)節(jié),不受《Java虛擬機(jī)規(guī)范》
- HotSpot中方法區(qū)的變化
永久代為什么要被元空間替換?
參考
- 隨著Java 8的到來(lái),HotSpot VM中再也見(jiàn)不到永久代了,但是這并不意味著類(lèi)的元數(shù)據(jù)信息也消失了。這些數(shù)據(jù)被移到了一個(gè)與堆不相連的本地內(nèi)存區(qū)域。這個(gè)區(qū)域叫做元空間
- 由于類(lèi)的元數(shù)據(jù)分配在本地內(nèi)存中,元空間的最大可分配空間就是系統(tǒng)的可用內(nèi)存空間
- 這項(xiàng)改動(dòng)是很有必要的,因?yàn)椋?/li>
- 為永久代設(shè)置空間大小是很難確定的。在某些場(chǎng)景下,如果動(dòng)態(tài)加載類(lèi)過(guò)多,容易產(chǎn)生Perm區(qū)的OOM。比如某個(gè)實(shí)際web工程中,因?yàn)楣δ茳c(diǎn)比較多,在運(yùn)行過(guò)程中,要不斷動(dòng)態(tài)加載很多類(lèi),經(jīng)常出現(xiàn)致命錯(cuò)誤。而元空間和永久代之間最大的區(qū)別在于:元空間并不在虛擬機(jī)中,而是使用本地內(nèi)存。因此,默認(rèn)情況下,元空間的大小僅受本地內(nèi)存限制
- 對(duì)永久代進(jìn)行調(diào)優(yōu)是很困難的
StringTable為什么要調(diào)整
- JDK7中將StringTable放到了堆空間中。因?yàn)橛谰么幕厥招屎艿?,在Full GC的時(shí)候才會(huì)觸發(fā)。而Full GC是老年代的空間不足,永久代不足時(shí)才會(huì)觸發(fā)。這就導(dǎo)致StringTable回收效率不高。而我們開(kāi)發(fā)中會(huì)有大量的字符串被創(chuàng)建,回收效率低,導(dǎo)致永久代內(nèi)存不足。放到堆里,能及時(shí)回收內(nèi)存
代碼測(cè)試1: JDK7
package com.na7;
public class StaticFieldTest {
private static byte[] arr = new byte[1024 * 1024 * 100]; // 100m
public static void main(String[] args) {
Sy);
}
}
在JDK1.7下,運(yùn)行參數(shù)為-Xms200m -Xmx200m -XX:PermSize=300m -XX:MaxPermSize=300m -XX:+PrintGCDetails,輸出如下:
[B@451dfada
Heap
PSYoungGen total 60416K, used 3133K [0x00000007fbd00000, 0x0000000800000000, 0x0000000800000000)
eden space 52224K, 6% used [0x00000007fbd00000,0x00000007fc00f718,0x00000007ff000000)
from space 8192K, 0% used [0x00000007ff800000,0x00000007ff800000,0x0000000800000000)
to space 8192K, 0% used [0x00000007ff000000,0x00000007ff000000,0x00000007ff800000)
ParOldGen total 136704K, used 102400K [0x00000007f3780000, 0x00000007fbd00000, 0x00000007fbd00000)
object space 136704K, 74% used [0x00000007f3780000,0x00000007f9b80010,0x00000007fbd00000)
PSPermGen total 307200K, used 2628K [0x00000007e0b80000, 0x00000007f3780000, 0x00000007f3780000)
object space 307200K, 0% used [0x00000007e0b80000,0x00000007e0e11050,0x00000007f3780000)
可以看到ParOldGen total 136704K, used 102400K arr數(shù)組放在了老年代中
同樣代碼運(yùn)行在JDK1.8下面,運(yùn)行參數(shù)為-Xms200m -Xmx200m -XX:MetaspaceSize=300m -XX:MaxMetaspace=300m -XX:+PrintGCDetails,輸出如下:
[B@2503dbd3
Heap
PSYoungGen total 59904K, used 3103K [0x00000007bbd80000, 0x00000007c0000000, 0x00000007c0000000)
eden space 51712K, 6% used [0x00000007bbd80000,0x00000007bc087c70,0x00000007bf000000)
from space 8192K, 0% used [0x00000007bf800000,0x00000007bf800000,0x00000007c0000000)
to space 8192K, 0% used [0x00000007bf000000,0x00000007bf000000,0x00000007bf800000)
ParOldGen total 136704K, used 102400K [0x00000007b3800000, 0x00000007bbd80000, 0x00000007bbd80000)
object space 136704K, 74% used [0x00000007b3800000,0x00000007b9c00010,0x00000007bbd80000)
Metaspace used 2658K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 287K, capacity 386K, committed 512K, reserved 1048576K
可以看到byte數(shù)組依然放在老年代ParOldGen total 136704K, used 102400K。
如果測(cè)試JDK1.6版本下,依然可以看到byte數(shù)組放在了老年代。應(yīng)注意,我們提到JDK1.7以后靜態(tài)變量才存放在堆空間,指的是引用變量,而靜態(tài)引用對(duì)應(yīng)的對(duì)象實(shí)體始終都存在堆空間。即只要是對(duì)象實(shí)例必然會(huì)在Java堆中分配
7. 方法區(qū)的垃圾回收
有些人認(rèn)為方法區(qū)(如HotSpot虛擬機(jī)中的元空間或者永久代)是沒(méi)有垃圾收集行為的,其實(shí)不然?!禞ava虛擬機(jī)規(guī)范》對(duì)方法區(qū)的約束是非常寬松的,提到過(guò)尅不要求虛擬機(jī)在方法區(qū)中實(shí)現(xiàn)垃圾收集。事實(shí)上也確實(shí)有未實(shí)現(xiàn)或未能完整實(shí)現(xiàn)方法區(qū)類(lèi)型卸載的收集器存在(如JDK 11時(shí)期ZGC收集器就不支持類(lèi)卸載)
一般來(lái)說(shuō)這個(gè)區(qū)域的回收效果比較難令人滿意,尤其是類(lèi)型的卸載,條件相當(dāng)苛刻。但是這部分區(qū)域的回收有時(shí)又確實(shí)是必要的。以前Sun公司的Bug列表中,曾出現(xiàn)過(guò)的若干個(gè)嚴(yán)重的bug就是由于低版本的HotSpot虛擬機(jī)對(duì)此區(qū)域未完全收集而導(dǎo)致的內(nèi)存泄漏
方法區(qū)的垃圾收集主要回收兩部分內(nèi)容:常量池中廢棄的常量和不再使用的類(lèi)型
- 先來(lái)說(shuō)說(shuō)方法區(qū)內(nèi)常量池之中主要存放的兩大類(lèi)常量:字面量和符號(hào)引用。字面量比較接近Java語(yǔ)言層次的常量概念,如文本字符串、被聲明為final的常量值等。而符號(hào)引用則屬于編譯原理方面的概念,也包括下面三類(lèi)常量:
- 類(lèi)和接口的全限定名
- 字段的名稱(chēng)和描述符
- 方法的名稱(chēng)和描述符
- HotSpot虛擬機(jī)對(duì)常量池的回收策略是很明確的,只要常量池中的常量沒(méi)有被任何地方引用,就可以被回收
- 回收廢棄常量與回收J(rèn)ava堆中的對(duì)象非常類(lèi)似
判定一個(gè)常量是否“廢棄”還是相對(duì)簡(jiǎn)單,而要判定一個(gè)類(lèi)型是否屬于“不再被使用的類(lèi)”的條件就比較苛刻。需要同時(shí)滿足下面三個(gè)條件: - 該類(lèi)所有的實(shí)例都已經(jīng)被回收,也就是Java堆中不存在該類(lèi)及其任何派生子類(lèi)的實(shí)例
- 加載該類(lèi)的類(lèi)加載器已經(jīng)被回收,這個(gè)條件除非是經(jīng)過(guò)精心設(shè)計(jì)的可替換類(lèi)加載器的場(chǎng)景,如OSGI,JSP的重加載等,否則通常是很難達(dá)成的
- 該類(lèi)對(duì)應(yīng)的java.lang.Class對(duì)象沒(méi)有在任何地方被引用,無(wú)法在任何地方通過(guò)反射訪問(wèn)該類(lèi)的方法
- Java虛擬機(jī)被允許對(duì)滿足上述三個(gè)條件的無(wú)用類(lèi)進(jìn)行回收,這里說(shuō)的僅僅是“被允許”,而不是和對(duì)象一樣,沒(méi)有引用了就必然會(huì)被回收。關(guān)于是否要對(duì)類(lèi)型進(jìn)行回收,Hotspot虛擬機(jī)提供了-Xnoclassgc參數(shù)進(jìn)行控制,還可以使用-verbose:class以及-XX:+TraceClass-Loading、-XX:+TraceClassUnLoading查看類(lèi)加載和卸載信息
- 在大量使用反射、動(dòng)態(tài)代理、CGLib等字節(jié)碼框架,動(dòng)態(tài)生成JSP以及OSGI這類(lèi)頻繁自定義類(lèi)加載器的場(chǎng)景中,通常都需要Java虛擬機(jī)具備類(lèi)型卸載的能力,以保證不會(huì)對(duì)方法區(qū)造成過(guò)大的內(nèi)存壓力
1.《00000007b怎么解決?終于找到答案了JVM系列(七)運(yùn)行時(shí)數(shù)據(jù)區(qū)(方法區(qū))》援引自互聯(lián)網(wǎng),旨在傳遞更多網(wǎng)絡(luò)信息知識(shí),僅代表作者本人觀點(diǎn),與本網(wǎng)站無(wú)關(guān),侵刪請(qǐng)聯(lián)系頁(yè)腳下方聯(lián)系方式。
2.《00000007b怎么解決?終于找到答案了JVM系列(七)運(yùn)行時(shí)數(shù)據(jù)區(qū)(方法區(qū))》僅供讀者參考,本網(wǎng)站未對(duì)該內(nèi)容進(jìn)行證實(shí),對(duì)其原創(chuàng)性、真實(shí)性、完整性、及時(shí)性不作任何保證。
3.文章轉(zhuǎn)載時(shí)請(qǐng)保留本站內(nèi)容來(lái)源地址,http://f99ss.com/gl/2198167.html