面試官:說下你對(duì)方法區(qū)演變過程和內(nèi)部結(jié)構(gòu)的理解


簡介


永久代、元空間
方法區(qū)是 Java 虛擬機(jī)規(guī)范中的概念,而永久代和元空間是 HotSpot 虛擬機(jī)對(duì)方法區(qū)的一種實(shí)現(xiàn)。通俗點(diǎn)講:如果把方法區(qū)比作接口的話,那永久代和元空間可以比作實(shí)現(xiàn)該接口的實(shí)現(xiàn)類。
直接內(nèi)存
方法區(qū)的大小
jdk7 及以前
-XX:PermSize=N?//方法區(qū)?(永久代)?初始分配空間,默認(rèn)值為?20.75M
-XX:MaxPermSize=N //方法區(qū)?(永久代)?最大可分配空間。32位機(jī)器默認(rèn)是64M,64位機(jī)器默認(rèn)是82M
jdk8及以后
-XX:MetaspaceSize=N //方法區(qū)?(元空間)?初始分配空間,如果未指定此標(biāo)志,則元空間將根據(jù)運(yùn)行時(shí)的應(yīng)用程序需求動(dòng)態(tài)地重新調(diào)整大小。
-XX:MaxMetaspaceSize=N?//方法區(qū)?(元空間)?最大可分配空間,默認(rèn)值為?-1,即沒有限制
永久代:OutOfMemoryError:PermGen space 元空間:OutOfMemoryError:Metaspace
jvisualvm
public?class?MethodAreaDemo1?{
????public?static?void?main(String[]?args)?{
????????System.out.println("start...");
????????try?{
????????????Thread.sleep(1000000);
????????}?catch?(InterruptedException?e)?{
????????????e.printStackTrace();
????????}
????????System.out.println("end...");
????}
}

高水位線
XX:MetaspaceSize=21?就是初始的高水位線,一旦觸及這個(gè)水位線,F(xiàn)ull GC 將會(huì)被觸發(fā)并卸載沒用的類(即這些類對(duì)應(yīng)的類加載器不再存活),然后這個(gè)高水位線將會(huì)重置。如果釋放的空間不足,那么在不超過 MaxMetaspaceSize 時(shí),適當(dāng)提高該值; 如果釋放空間過多,則適當(dāng)降低該值。
如果初始化的高水位線設(shè)置過低,高水位線調(diào)整情況會(huì)發(fā)生很多次。通過垃圾回收器的日志可以觀察到 Full GC 多次調(diào)用。為了避免頻繁地GC,建議將? -XX :MetaspaceSize?設(shè)置為一個(gè)相對(duì)較高的值。
內(nèi)部結(jié)構(gòu)

類型信息
這個(gè)類型的完整有效名稱(全名=包名.類名) 這個(gè)類型直接父類的完整有效名(對(duì)于 interface 或是 java. lang.Object ,都沒有父類) 這個(gè)類型的修飾符( public , abstract, final 的某個(gè)子集) 這個(gè)類型直接接口的一個(gè)有序列表
域(Field)信息
JVM必須在方法區(qū)中保存類型的所有域(field,也稱為屬性)的相關(guān)信息以及域的聲明順序; 域的相關(guān)信息包括:域名稱、 域類型、域修飾符(public, private,protected, static, final, volatile, transient 的某個(gè)子集)
方法(Method)信息
方法名稱 方法的返回類型(或void) 方法參數(shù)的數(shù)量和類型(按順序) 方法的修飾符(public, private, protected, static, final,synchronized, native , abstract 的一個(gè)子集) 方法的字節(jié)碼(bytecodes)、操作數(shù)棧、局部變量表及大小( abstract 和 native 方法除外) 異常表( abstract 和 native 方法除外)每個(gè)異常處理的開始位置、結(jié)束位置、代碼處理在程序計(jì)數(shù)器中的偏移地址、被捕獲的異常類的常量池索引
non-final 的類變量
靜態(tài)變量和類關(guān)聯(lián)在一起,隨著類的加載而加載,他們成為類數(shù)據(jù)在邏輯上的一部分 類變量被類的所有實(shí)例所共享,即使沒有類實(shí)例你也可以訪問它。
public?class?MethodAreaDemo2?{
????public?static?void?main(String[]?args)?{
????????Order?order?=?null;
????????order.hello();
????????System.out.println(order.count);
????}
}
class?Order?{
????public?static?int?count?=?1;
????public?static?final?int?number?=?2;
????public?static?void?hello()?{
????????System.out.println("hello!");
????}
}
hello!
1
javap -v -p MethodAreaDemo2.class?命令
運(yùn)行時(shí)常量池
常量池表

為什么字節(jié)碼文件需要常量池?
運(yùn)行時(shí)常量池
Runtime Constant Pool)是方法區(qū)的一部分,類加載器加載字節(jié)碼文件時(shí),將常量池表加載進(jìn)方法區(qū)的運(yùn)行時(shí)常量池。運(yùn)行時(shí)常量池中包含多種不同的常量,包括編譯期就已經(jīng)明確的數(shù)值字面量,也包括到運(yùn)行期解析后才能夠獲得的方法或者字段引用。此時(shí)不再是常量池中的符號(hào)地址了,這里換為真實(shí)地址。運(yùn)行時(shí)常量池,相對(duì)于 Class 文件常量池的另一重要特征是:具備動(dòng)態(tài)性,比如? String.intern()。
演進(jìn)細(xì)節(jié)
jdk1.6 及之前:有永久代?,靜態(tài)變量存放在永久代上; jdk1.7:有永久代,但已經(jīng)逐步“去永久代”,字符串常量池、靜態(tài)變量移除,保存在堆中; jdk1.8及之后:無永久代,類型信息、字段、方法、常量保存在本地內(nèi)存的元空間,但字符串常量池、靜態(tài)變量仍在堆中;
演變示例圖



為什么要將永久代替換為元空間呢?
永久代使用的是 JVM 的內(nèi)存,受 JVM 設(shè)置的內(nèi)存大小限制;元空間使用的是本地直接內(nèi)存,它的最大可分配空間是系統(tǒng)可用內(nèi)存的空間。因?yàn)樵臻g里存放的是類的元數(shù)據(jù),所以隨著內(nèi)存空間的增大,能加載的類就更多了,相應(yīng)的溢出的機(jī)率會(huì)大大減小。 在 JDK8,合并 HotSpot 和 JRockit 的代碼時(shí),JRockit 從來沒有一個(gè)叫永久代的東西,合并之后就沒有必要額外的設(shè)置這么一個(gè)永久代的地方了。 對(duì)永久代進(jìn)行調(diào)優(yōu)是很困難的。
StringTable 為什么要調(diào)整
StringTable?回收效率不高。而我們開發(fā)中會(huì)有大量的字符串被創(chuàng)建,回收效率低,導(dǎo)致永久代內(nèi)存不足。放到堆里,能及時(shí)回收內(nèi)存。垃圾回收
字面量比較接近 Java 語言層次的常量概念,如文本字符串、被聲明為 final 的常量值等。 符號(hào)引用則屬于編譯原理方面的概念,包括類和接口的全限定名、字段的名稱和描述符、方法的名稱和描述符。
類型判定
該類所有的實(shí)例都已經(jīng)被回收,也就是 Java 堆中不存在該類及其任何派生子類的實(shí)例; 加載該類的類加載器已經(jīng)被回收,這個(gè)條件除非是經(jīng)過精心設(shè)計(jì)的可替換類加載器的場景,如OSGi、JSP的重加載等,否則通常是很難達(dá)成的; 該類對(duì)應(yīng)的 java.lang.Class 對(duì)象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。

?推薦閱讀

華為最美小姐姐被外派墨西哥后...

國內(nèi)有程序員電視劇了,結(jié)果看了一分鐘,就吐了...

男女洗澡前后區(qū)別,太形象了!
END


頂級(jí)程序員:topcoding
做最好的程序員社區(qū):Java后端開發(fā)、Python、大數(shù)據(jù)、AI
一鍵三連「分享」、「點(diǎn)贊」和「在看」
評(píng)論
圖片
表情
