JVM—方法区到底是怎么保存函数方法的?
原文作者:博_采_眾_長(zhǎng)
原文地址:JVM --方法區(qū)(超詳細(xì))
目錄
HotSpot中方法區(qū)的演進(jìn)
方法區(qū)的內(nèi)部結(jié)構(gòu)
運(yùn)行時(shí)常量池 vs 常量池
方法區(qū)的演進(jìn)細(xì)節(jié)
StringTable為什么要調(diào)整
最近想深入了解Java多太的重載和重寫機(jī)制是如何實(shí)現(xiàn)的,既然說到重載,那肯定要了解函數(shù)是如何加載的,加載到哪里,加載之后的形式是什么,如何保存在內(nèi)存中的?只有了解了函數(shù)的加載,才能談重載是什么?于是就有了此篇文章,這篇文章主要解答的事后面兩個(gè)問題,即加載之后的形式是什么,如何保存在內(nèi)存中的。關(guān)于函數(shù)是如何加載的,加載到哪里,推薦閱讀java.lang包—類加載器ClassLoader類,這篇文章詳細(xì)講解了JVM如何把函數(shù)加載到內(nèi)存。
運(yùn)行時(shí)數(shù)據(jù)區(qū)結(jié)構(gòu)圖
棧、堆、方法區(qū)的交互關(guān)系
方法區(qū)看作是一塊獨(dú)立與Java堆的內(nèi)存空間
?
- 方法區(qū)(Method Area)與Java堆一樣,是各個(gè)線程共享的內(nèi)存區(qū)域
- 方法區(qū)在JVM啟動(dòng)的時(shí)候被創(chuàng)建,并且它的實(shí)際物理內(nèi)存空間中和Java堆區(qū)一樣都可以是不連續(xù)的
- 方法區(qū)的大小,跟堆空間一樣,可以選擇固定大小或者可擴(kuò)展
- 方法區(qū)的大小決定了系統(tǒng)可以保存多少個(gè)類,如果系統(tǒng)定義了太多的類,導(dǎo)致方法區(qū)溢出,虛擬機(jī)同樣會(huì)拋出內(nèi)存溢出錯(cuò)誤:java.lang.OutOfMemoryError:PermGen space 或者 java.lang.OutOfMemoryError:Metaspace
- ????????加載大量的第三方的jar包;Tomcat部署的工程過多(30-50個(gè));大量動(dòng)態(tài)的生成反射類
- 關(guān)閉JVM就會(huì)釋放這個(gè)區(qū)域的內(nèi)存
HotSpot中方法區(qū)的演進(jìn)
在jdk7及以前,習(xí)慣上把方法區(qū),稱為永久代。jdk8開始,使用元空間取代了永久代。本質(zhì)上,方法區(qū)和永久代并不等價(jià)。僅是對(duì)HotSpot而言1.《Java虛擬機(jī)規(guī)范》對(duì)如何實(shí)現(xiàn)方法區(qū),不作統(tǒng)一要求。例如:BEA JRockit / IBM J9中不存在永久代的概念。現(xiàn)在看來,當(dāng)年使用永久代,不是好的idea。導(dǎo)致Java程序更容易OOM(超過-XX:MaxPermSize上限)而到了JDK 8,終于完放棄了永久代的概念,改用與JRockit、J9一樣在本地內(nèi)存中實(shí)現(xiàn)的元空間(Metaspace)來代替。
元空間的本質(zhì)和永久代類似,都是對(duì)JVM規(guī)范中方法區(qū)的實(shí)現(xiàn)。不過元空間與永久代最大的區(qū)別在于:元空間不在虛擬機(jī)設(shè)置的內(nèi)存中、而是使用本地內(nèi)存。永久代、元空間二者并不只是名字變了,內(nèi)存結(jié)構(gòu)也調(diào)整了。根據(jù)《Java虛擬機(jī)規(guī)范》的規(guī)定,如果方法區(qū)無法滿足新的內(nèi)存分配需求時(shí),將拋出OOM異常。
方法區(qū)的內(nèi)部結(jié)構(gòu)
《深入理解Java虛擬機(jī)》書中對(duì)方法區(qū)(Method Area)存儲(chǔ)內(nèi)容描述如下:它用于存儲(chǔ)虛擬機(jī)加載的類型信息、常量、靜態(tài)常量、即時(shí)編譯器編譯后的代碼緩存等。
1)類型信息
對(duì)每個(gè)加載的類型(類class、接口interface、枚舉enum、注解annotation),JVM必須在方法區(qū)中存儲(chǔ)以下類型信息:
2)域(Field)信息
- JVM必須在方法區(qū)中保存類型的所有域的相關(guān)信息以及域的聲明順序。
- 域的相關(guān)信息包括:域名稱、域類型、域修飾符(public,private,protected,static,final,volate,transient的某個(gè)子集)
3)方法(Method)信息
JVM必須保存所有方法的以下信息,同域信息一樣包括聲明順序;
- 方法名稱
- 方法的返回類型(或 void)
- 方法參數(shù)的數(shù)量和類型(按順序)
- 方法的修飾符(public,private,protected,static,final,synchronized,native,abstract的一個(gè)子集)
- 方法的字節(jié)碼(bytecodes)、操作數(shù)棧、局部變量表及大小(abstract和native方法除外)
- 異常表(abstract和native方法除外)
- 每個(gè)異常處理的開始位置、結(jié)束位置、代碼處理在程序計(jì)數(shù)器中的偏移地址、被捕獲的異常類的常量池索引
4)non-fianl的類變量
- 靜態(tài)變量和類關(guān)聯(lián)在一起,隨著類的加載而加載,它們成為類數(shù)據(jù)在邏輯上的一部分
- 類變量被類的所有實(shí)例共享,即使沒有實(shí)例時(shí)你也可以訪問它
5)補(bǔ)充說明:全局常量:static final
被聲明為fianl的類變量的處理方法則不同,每個(gè)全局常量在編譯的時(shí)候就會(huì)被分配了。
運(yùn)行時(shí)常量池 vs 常量池
- 方法區(qū),內(nèi)部包含了運(yùn)行時(shí)常量池
- 字節(jié)碼文件,內(nèi)部包含了常量池
- 要弄清除方法區(qū),需要理解清除ClassFile,因?yàn)榧虞d類的信息都在方法區(qū)
- 要弄清楚方法區(qū)的運(yùn)行時(shí)常量池,需要理解清除ClassFile中的常量池
一個(gè)有效的字節(jié)碼文件中除了包含類的版本信息、字段、方法以及接口等描述信息外,還包含一項(xiàng)信息那就是常量池表(Constant Pool Table),包含各種字面量和對(duì)類型、域和方法的符號(hào)引用。
1)為什么需要常量池?
一個(gè)java源文件中的類、接口,編譯后產(chǎn)生一個(gè)字節(jié)碼文件。而Java中的字節(jié)碼需要數(shù)據(jù)支持,通常這種數(shù)據(jù)會(huì)很大以至于不能直接存在字節(jié)碼里,換另一種方式,可以存到常量池,這個(gè)字節(jié)碼包含了指向常量池的引用。在動(dòng)態(tài)鏈接的時(shí)候回用到運(yùn)行時(shí)常量池。
2)常量池中有什么?
- 數(shù)量值
- 字符串值
- 類引用
- 字段引用
- 方法引用
結(jié)論
???????常量池,可以看作是一張表,虛擬機(jī)指令根據(jù)這張常量表找到要執(zhí)行的類名、方法名、參數(shù)類型、字面量等類型。
運(yùn)行時(shí)常量池
- 運(yùn)行時(shí)常量池(Runtime Constant Pool)是方法區(qū)的一部分
- 常量池表(Constant Pool Table)是Class文件的一部分,用于存放編譯期生成的各種字面量與符號(hào)引用,這部分內(nèi)容將在類加載后存放到方法區(qū)的運(yùn)行時(shí)常量池中
- 運(yùn)行時(shí)常量池,在加載類和接口到虛擬機(jī)后,就會(huì)創(chuàng)建對(duì)應(yīng)的運(yùn)行時(shí)常量池
- JVM為每個(gè)已加載的類型(類或接口)都維護(hù)一個(gè)常量池。池中的數(shù)據(jù)項(xiàng)就像數(shù)組項(xiàng)一樣,是通過索引訪問的
- 運(yùn)行時(shí)常量池中包含多種不同的常量,包括編譯器期就已經(jīng)明確的數(shù)值字面量,也包括到運(yùn)行期解析后才能夠獲得的方法或者字段引用。此時(shí)不再是常量池中的符號(hào)地址了,這里換為真實(shí)地址。
- ????????運(yùn)行時(shí)常量池,相對(duì)于Class文件常量池的另一重要特性是:具備動(dòng)態(tài)性
- 運(yùn)行時(shí)常量池類似于傳統(tǒng)編程語言中的符號(hào)表(symbol table),但是它所包含的數(shù)據(jù)卻比符號(hào)表要更加豐富一些
- 當(dāng)創(chuàng)建類或接口的運(yùn)行時(shí)常量池時(shí),如果構(gòu)造運(yùn)行時(shí)常量池所需要的內(nèi)存空間超過了方法區(qū)所能提供的最大值,則JVM會(huì)拋OutOfMemoryError異常。
方法區(qū)的演進(jìn)細(xì)節(jié)
1.首先說明:只有HotSpot才有永久代
BEA JRockit、IBM J9等來說,是不存在永久代的概念的。原則上如何實(shí)現(xiàn)方法區(qū)屬于虛擬機(jī)實(shí)現(xiàn)襲擊,不受《Java虛擬機(jī)規(guī)范》管束,并不要求統(tǒng)一。
2.HotSpot中方法區(qū)的1變化:
永久代為什么要被元空間替換?
隨著Java8的到來,HotSpot VM 中再也見不到永久代了。但是這并不意味這類的元數(shù)據(jù)信息也消失了。這些數(shù)據(jù)被轉(zhuǎn)移到了一個(gè)與堆不相連的本地內(nèi)存區(qū)域,這個(gè)區(qū)域叫做元空間(Metaspace)。由于類的元數(shù)據(jù)分配在本地內(nèi)存中,元空間的最大可分配空間就是系統(tǒng)可用內(nèi)存空間。這項(xiàng)改動(dòng)是很有重要的,原因有:
?1??為永久代設(shè)置空間大小是很難確定的
在某些場(chǎng)景下,如果動(dòng)態(tài)加載類過多,容易產(chǎn)生Perm 區(qū)的OOM。比如某個(gè)實(shí)際Web工程中,因?yàn)楣δ茳c(diǎn)比較多,在運(yùn)行過程中,要不斷動(dòng)態(tài)加載很多類,經(jīng)常出現(xiàn)致命錯(cuò)誤。而元空間和永久代之間最大的區(qū)別就在:元空間并不在虛擬機(jī)中,而是使用本地內(nèi)存。因此,默認(rèn)情況下,元空間的大小僅受本地內(nèi)存限制
2??對(duì)永久代進(jìn)行調(diào)優(yōu)是很困難的
方法區(qū)的垃圾收集主要回收兩部分內(nèi)容:常量池中廢棄的常量和不再使用的類型。
判定一個(gè)常量是否“廢棄”還是相對(duì)簡(jiǎn)單,而要判斷一個(gè)類型是都屬于“不在被使用的類”的條件就比較苛刻了。需要同時(shí)滿足下面三個(gè)條件;
?????????改類所有的實(shí)例都已經(jīng)被回收,也就是Java堆中不存在該類及其任何派生類的實(shí)例
?????????加載該類的類加載器已經(jīng)被回收,這個(gè)條件除非是經(jīng)過精心設(shè)計(jì)的可替換類加載器的場(chǎng)景,如OSGi、JSP的重加載等,否則是很難達(dá)成的。
?????????該類對(duì)應(yīng)的java.lang.Class對(duì)象沒有任何地方被引用,無法再任何地方通過反射訪問該類的方法
在使用大量反射、動(dòng)態(tài)代理、CGLib等字節(jié)碼框架,動(dòng)態(tài)生成JSP以及OSGi這類頻繁自定義類加載器的場(chǎng)景中,通常都需要Java虛擬機(jī)具備類型卸載的能力,以保證不會(huì)對(duì)方法區(qū)造成過大的內(nèi)存壓力。
StringTable為什么要調(diào)整
jdk7將StringTable放到了對(duì)空間中。因?yàn)橛谰么幕厥招屎艿?#xff0c;在Full GC的時(shí)候才會(huì)觸發(fā)。而Full GC是老年代的空間不足、永久代不足時(shí)才會(huì)觸發(fā)。這就導(dǎo)致StringTable回收效率不高。而我們開發(fā)中會(huì)有大量的字符串被創(chuàng)建,回收效率低,導(dǎo)致永久代內(nèi)存不足。放到堆里,能及時(shí)回收內(nèi)存。
總結(jié)
以上是生活随笔為你收集整理的JVM—方法区到底是怎么保存函数方法的?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JVM—如何利用虚拟机栈进行函数调用?
- 下一篇: JVM—加载到方法区的Class文件长什