方法區(qū)
棧、堆捞魁、方法區(qū)的交互關(guān)系
方法區(qū)在哪里佃延?
《Java虛擬機規(guī)范》中明確說明:“盡管所有的方法區(qū)在邏輯上是屬于堆的一- 部分,但.一些簡單的實現(xiàn)可能不會選擇去進(jìn)行垃圾收集或者進(jìn)行壓縮脸甘。”但對 于Hotspot JVM而言偏灿,方法區(qū)還有一個別名叫做Non-Heap (非堆)丹诀,目的就是要和堆分開。
所以翁垂,方法區(qū)看作是一塊獨立于Java堆的內(nèi)存空間铆遭。
方法區(qū)的基本理解
●?方法區(qū)(Method Area) 與Java堆一樣,是各個線程共享的內(nèi)存區(qū)域沿猜。
●?方法區(qū)在JVM啟動的時候被創(chuàng)建枚荣,并且它的實際的物理內(nèi)存空間中和Java堆區(qū)一樣都可以是不連續(xù)的。
●?方法區(qū)的大小啼肩,跟堆空間一樣橄妆,可以選擇固定大小或者可擴展衙伶。
●?方法區(qū)的大小決定了系統(tǒng)可以保存多少個類,如果系統(tǒng)定義了太多的類呼畸,導(dǎo)致方法區(qū)溢出痕支,虛擬機同樣會拋出內(nèi)存溢出錯誤: java.lang.OutOfMemoryError:PermGen space 或者j ava.lang .OutOfMemoryError: Metaspace
●?關(guān)閉JVM就會釋放這個區(qū)域的內(nèi)存。
Hostpot中方法區(qū)的演進(jìn)
●?在jdk7及以前蛮原,習(xí)慣上把方法區(qū)卧须,稱為永久代。jdk8開始儒陨,使用元空間取代了永久代花嘶。
???現(xiàn)在來看研铆,當(dāng)年使用永久代埋同,不是好的idea。 導(dǎo)致Java程序更容易OOM (超過-XX: MaxPermSize.上限)
●?而到了JDK8棵红,終于完全廢棄了永久代的概念凶赁,改用與JRockit、J9一樣在本地內(nèi)存中實現(xiàn)的元空間( Metaspace)來代替
●?永久代咏窿、元空間二者并不只是名字變了,內(nèi)部結(jié)構(gòu)也調(diào)整了素征。
●?根據(jù)《Java虛擬機規(guī)范》的規(guī)定集嵌,如果方法區(qū)無法滿足新的內(nèi)存分配需求時,將拋出OOM異常稚茅。
方法區(qū)概述
設(shè)置方法區(qū)內(nèi)存的大小
●?方法區(qū)的大小不必是固定的纸淮,jvm可以根據(jù)應(yīng)用的需要動態(tài)調(diào)整平斩。
●?jdk7及以前:
???通過-XX:PermSize來設(shè)置永久代初始分配空間亚享。默認(rèn)值是20.75M
???-XX:MaxPermSize來設(shè)定永久代最大可分配空間。32位機器默認(rèn)是64M绘面,64位機器模式是
82M
???當(dāng)JVM加載的類信息容量超過了這個值欺税,會報異常OutOfMemoryError : PermGen space侈沪。
●?jdk8及以后:
???元數(shù)據(jù)區(qū)大小可以使用參數(shù)-XX :MetaspaceSize和-XX :MaxMetaspaceSize指定,替代上述原有的兩個參數(shù)。
???默認(rèn)值依賴于平臺晚凿。windows下亭罪,-XX:MetaspaceSize是21M, -XX :MaxMetaspaceSize的值是-1歼秽,即沒有限制应役。
???與永久代不同,如果不指定大小燥筷,默認(rèn)情況下箩祥,虛擬機會耗盡所有的可用系統(tǒng)內(nèi)存。如果元數(shù)據(jù)區(qū)發(fā)生溢出肆氓,虛擬機一樣會拋出異常OutOfMemoryError: Metaspace
???-XX:MetaspaceSize:設(shè)置初始的元空間大小袍祖。對于一個64位的服務(wù)器端JVM來說,其默認(rèn)的-XX : MetaspaceSize值為21MB谢揪。這就是初始的高水位線蕉陋,一旦觸及這個水位線,F(xiàn)ullGC將會被觸發(fā)并卸載沒用的類(即這些類對應(yīng)的類加載器不再存活)拨扶,然后這個高水位線將會重置凳鬓。新的高水位線的值取決于GC后釋放了多少元空間。如果釋放的空間不足屈雄,那么在不超過MaxMetaspaceSize時村视,適當(dāng)提高該值。如果釋放
空間過多酒奶,則適當(dāng)降低該值蚁孔。
???如果初始化的高水位線設(shè)置過低,上述高水位線調(diào)整情況會 發(fā)生很多次惋嚎。通過垃圾回收器的日志可以觀察到Full GC多次調(diào)用杠氢。為了避免頻繁地GC,建議將-XX :MetaspaceSize設(shè)置為一個相對較高的值另伍。
如何解決這些OOM
1鼻百、要解決OOM異常或heap space的異常摆尝, 一般的手段是首先通過內(nèi)存映像分析工具(如Eclipse Memory Analyzer)對dump出來的堆轉(zhuǎn)儲快照進(jìn)行分析温艇,重點是確認(rèn)內(nèi)存中的對象是否是必要的,也就是要先分清楚到底是出現(xiàn)了內(nèi)存泄漏(MemoryLeak)還是內(nèi)存溢出(Memory Overflow)堕汞。
2勺爱、如果是內(nèi)存泄漏,可進(jìn)一步通過工具查看泄漏對象到GC Roots 的引用鏈讯检。于是就能找到泄漏對象是通過怎樣的路徑與GCRoots相關(guān)聯(lián)并導(dǎo)致垃圾收集器無法自動回收它們的琐鲁。掌握了泄漏對象的類型信息卫旱,以及GC Roots引用鏈的信息,就可以比較準(zhǔn)確地定位出泄漏代碼的位置围段。
3顾翼、如果不存在內(nèi)存泄漏,換句話說就是內(nèi)存中的對象確實都還必須存活著奈泪,那就應(yīng)當(dāng)檢查虛擬機的堆參數(shù)(-Xmx與-Xms) 适贸,與機器物理內(nèi)存對比看是否還可以調(diào)大,從代碼上檢查是否存在某些對象生命周期過長涝桅、持有狀態(tài)時間過長的情況取逾,嘗試減少程序運行期的內(nèi)存消耗。
方法區(qū)的內(nèi)部結(jié)構(gòu)
方法區(qū)(Method Area)存儲什么苹支?
《深入理解Java虛擬機》書中對方法區(qū)(Method Area)存儲內(nèi)容描述如下:
它用于存儲已被虛擬機加載的類型信息砾隅、常量、靜態(tài)變量债蜜、即時編譯器編譯后的代碼緩存等晴埂。
類型信息
對每個加載的類型(類class、接口interface寻定、枚舉enun儒洛、 注解annlotation) ,JVM必須在方法區(qū)中存儲以下類型信息:
①?這個類型的完整有效名稱(全名=包名.類名)
②?這個類型直接父類的完整有效名(對于interface或是java . lang. objlct, 都沒有父類)
③?這個類型的修飾符(public, abstract, final的某個子集)
④?這個類型直接接口的一個有序列表
域(Field)信息
●?JVM必須在方法區(qū)中保存類型的所有域的相關(guān)信息以及域的聲明順序狼速。
●?域的相關(guān)信息包括: 域名稱琅锻、域類型、域修飾符(public, private, protected, static, final, volatile, transient的某個子集)
方法(Method)信息
JVM必須保存所有方法的以下信息向胡,同域信息一樣包括聲明順序:
●?方法名稱
●?方法的返回類型(或void)
●?方法參數(shù)的數(shù)量和類型(按順序)
●?方法的修飾符(public, private, protected, static, final, synchronized, native, abstract的一個子集)
●?方法的字節(jié)碼(bytecodes)恼蓬、操作數(shù)棧、局部變量表及大小(abstract和native方法除外)
●?異常表( abstract和native方法除外)
???每個異常處理的開始位置僵芹、結(jié)束位置处硬、代碼處理在程序計數(shù)器中的偏移地址、被捕獲的異常類的常量池索引
non-final的類變量
●?靜態(tài)變量和類關(guān)聯(lián)在一起拇派,隨著類的加載而加載,它們成為類數(shù)據(jù)在邏輯上的一部分荷辕。
●?類變量被類的所有實例共享,即使沒有類實例時你也可以訪問它件豌。
全局常量:static final
被聲明為final的類變量的處理方式則不同疮方,每個全局常量在編譯的時候就會被分配了
運行時常量池VS常量池
●?方法區(qū),內(nèi)部包含了運行時常量池茧彤。
●?字節(jié)碼文件骡显,內(nèi)部包含了常量池。
●?要弄清楚方法區(qū),需要理解清楚ClassFile蟆盐,因為加載類的信息都在方法區(qū)。
●?要弄清楚方法區(qū) 的運行時常量池遭殉,需要理解清楚ClassFile中的常量池石挂。
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html
一個有效的字節(jié)碼文件中除了包含類的版本信息、字段险污、方法以及接口等描述信息外痹愚,還包含一項信息那就是常量池表(Constant Pool Table) ,包括各種字面量和對類型蛔糯、域和方法的符號引用拯腮。
為什么需要常量池?
一個java源文件中的類蚁飒、接口动壤,編譯后產(chǎn)生一個字節(jié)碼文件。而Java中的字節(jié)碼需要數(shù)據(jù)支持淮逻,通常這種數(shù)據(jù)會很大以至于不能直接存到字節(jié)碼里琼懊,換另一種方式,可以存到常量池爬早,這個字節(jié)碼包含了指向常量池的引用哼丈。在動態(tài)鏈接的時候會用到運行時常量池,之前有介紹筛严。比如如下的代碼:雖然只有194字節(jié)醉旦,但是里面卻使用了String、System桨啃、PrintStream及object等結(jié)構(gòu)车胡。這里代碼量其實已經(jīng)很小了。如果代碼多照瘾,引用到的結(jié)構(gòu)會更多吨拍!這里就需要常量池了!
常量池中有什么网杆?
小結(jié):
常量池羹饰,可以看做是一張表,虛擬機指令根據(jù)這張常量表找到要執(zhí)行的類名碳却,方法名队秩,參數(shù)類型,字面量等類型
運行時常量池
●?運行時常量池( Runtime Constant Pool) 是方法區(qū)的一部分昼浦。
●?常量池表(Constant Pool Table) 是Class文件的一部分馍资,用于存放編譯期生成的各種字面量與符號引用,這部分內(nèi)容將在類加載后存放到方法區(qū)的運行時常量池中关噪。
●?運行時常量池鸟蟹,在加載類和接口到虛擬機后乌妙,就會創(chuàng)建對應(yīng)的運行時常量池。
●?JVM為每個己加載的類型(類或接口)都維護(hù)一個常量池建钥。池中的數(shù)據(jù)項像數(shù)組項一樣藤韵,是通過索引訪問的。
●?運行時常量池中包含多種不同的常量熊经,包括編譯期就已經(jīng)明確的數(shù)值字面量泽艘,也包括到運行期解析后才能夠獲得的方法或者字段引用。此時不再是常量池中的符號地址了镐依,這里換為真實地址匹涮。
???運行時常量池,相對于Class文件常量池的另--重要特征是:具備動態(tài)性槐壳。
●?運行時常量池類似于傳統(tǒng)編程語言中的符號表(symboltable)然低,但是它所包含的數(shù)
據(jù)卻比符號表要更加豐富一些。
●?當(dāng)創(chuàng)建類或接口的運行時常量池時务唐,如果構(gòu)造運行時常量池所需的內(nèi)存空間超過了方法
區(qū)所能提供的最大值脚翘,則JVM會 拋OutOfMemoryError異常。
方法區(qū)的演進(jìn)細(xì)節(jié)
1.首先明確:只有HotSpot才有永久代绍哎。BEA JRockit来农、 IBM J9等來說,是不存在永久代的概念的崇堰。原則上如何實現(xiàn)方法區(qū)屬于虛擬機實現(xiàn)細(xì)節(jié)沃于,不受《Java虛擬機規(guī)范》管束,并不要求統(tǒng)一 海诲。
-
Hotspot中方法區(qū)的變化:
永久代為什么要被元空間替換繁莹?
●?隨著Java8的到來,HotSpot VM中再也見不到永久代了特幔。但是這并不意味著類的元數(shù)據(jù)信息也消失了咨演。這些數(shù)據(jù)被移到了一個與堆不相連的本地內(nèi)存區(qū)域,這個區(qū)域叫做元空間( Metaspace )
●?由于類的元數(shù)據(jù)分配在本地內(nèi)存中蚯斯,元空間的最大可分配空間就是系統(tǒng)可用內(nèi)存空間薄风。
●?這項改動是很有必要的,原因有: .
???為永久代設(shè)置空間大小是很難確定的拍嵌。
在某些場景下遭赂,如果動態(tài)加載類過多,容易產(chǎn)生Perm區(qū)的00M横辆。比如某個實際web工程中撇他,因為功能點比較多,在運行過程中,要不斷動態(tài)加載很多類困肩,經(jīng)常出現(xiàn)致命錯誤划纽。
???對永久代進(jìn)行調(diào)優(yōu)是很困難的。
StringTable為什么要調(diào)整筋蓖?
jdk7中將StringTable放到了堆空間中卸耘。因為永久代的回收效率很低,在full gc的時候才會觸發(fā)粘咖。而full gc是老年代的空間不足蚣抗、永久代不足時才會觸發(fā)。這就導(dǎo)致StringTable回收效率不高瓮下。而我們開發(fā)中會有大量的字符串被創(chuàng)建翰铡,回收效率低,導(dǎo)致永久代內(nèi)存不足讽坏。放到堆里锭魔,能及時回收內(nèi)存。
方法區(qū)的垃圾收集
有些人認(rèn)為方法區(qū)(如HotSpot虛擬機中的元空間或者永久代)是沒有垃圾收集行為的路呜,其實不然迷捧。《Java虛擬機規(guī)范》 對方法區(qū)的約束是非常寬松的胀葱,提到過可以不要求虛擬機在方法區(qū)中實現(xiàn)垃圾收集漠秋。事實上也確實有未實現(xiàn)或未能完整實現(xiàn)方法區(qū)類型卸載的收集器存在(如JDK11時期的ZGc收集器就不支持類卸載)。
一般來說這個區(qū)域的回收效果比較難令人滿意抵屿,尤其是類型的卸載庆锦,條件相當(dāng)苛刻。但是這部分區(qū)域的回收有時又確實是必要的轧葛。以前Sun公司的Bug列表中搂抒,曾出現(xiàn)過的若干個嚴(yán)重的Bug就是由于低版本的HotSpot虛擬機對此區(qū)域未完全回收而導(dǎo)致內(nèi)存泄漏。
方法區(qū)的垃圾收集主要回收兩部分內(nèi)容:常量池中廢棄的常量和不再使用的類型尿扯。
●?先來說說方法區(qū)內(nèi)常量池之中主要存放的兩大類常量:字面量和符號引用燕耿。字面量比較接近Java語言層次的常量概念,如文本字符串姜胖、被聲明為final的常量值等誉帅。而符號引用則屬于編譯原理方面的概念,包括下面三類常量:
???1、類和接口的全限定名
???2蚜锨、字段的名稱和描述符
???3档插、方法的名稱和描述符
●?HotSpot虛擬機對常量池的回收策略是很明確的,只要常量池中的常量沒有被任何地方引用亚再,就可以被回收郭膛。
●?回收廢棄常量與回收J(rèn)ava堆中的對象非常類似。
●?判定一個常量是否“廢棄”還是相對簡單氛悬,而要判定一個類型是否屬于“不再被使用的類”的條件就比較苛刻了则剃。需要同時滿足下面三個條件:
???該類所有的實例都已經(jīng)被回收,也就是Java堆中不存在該類及其任何派生子類的實例如捅。
???加載該類的類加載器已經(jīng)被回收棍现,這個條件除非是經(jīng)過精心設(shè)計的可替換類加載器的場景,如OSGi镜遣、JSP的重加載等己肮,否則通常是很難達(dá)成的。
???該類對應(yīng)的java. lang. Class對象沒有在任何地方被引用悲关,無法在任何地方通過反射訪問該類的方法谎僻。
●?Java虛擬機被允許對滿足上述三個條件的無用類進(jìn)行回收,這里說的僅僅是“被允許”寓辱,而并不是和對象一樣艘绍,沒有引用了就必然會回收。關(guān)于是否要對類型進(jìn)行回收秫筏,HotSpot虛擬機提供了-Xnoclassgc參數(shù)進(jìn)行控制鞍盗,還可以使用-verbose:class以及-XX: +TraceClass-Loading、-XX:+TraceClassUnLoading 查看類加載和卸載信息
●?在大量使用反射跳昼、動態(tài)代理般甲、CGLib等字節(jié)碼框架,動態(tài)生成JSP以及oSGi這類頻繁自定義類加載器的場景中鹅颊,通常都需要Java虛擬機具備類型卸載的能力敷存,以保證不會對方法區(qū)造成過大的內(nèi)存壓力。
運行時數(shù)據(jù)區(qū)總結(jié)
常見面試題
百度
三面:說一下JVM內(nèi)存模型吧堪伍,有哪些區(qū)?分別干什么的?
螞蟻金服:
Java8的內(nèi)存分代改進(jìn)锚烦,
JVM內(nèi)存分哪幾個區(qū),每個區(qū)的作用是什么?
一面:JVM內(nèi)存 分布/內(nèi)存結(jié)構(gòu)?棧和堆的區(qū)別?堆的結(jié)構(gòu)?為什么兩個survivor區(qū)?
二面:Eden和Survior的比例分配
小米:
jvm內(nèi)存分區(qū)帝雇,為什么要有新生代和老年代
字節(jié)跳動:
二面:Java的內(nèi)存分區(qū)
二面:講講jvm運行時數(shù)據(jù)庫區(qū)
什么時候?qū)ο髸M(jìn)入老年代?
京東:
JVM的內(nèi)存結(jié)構(gòu)涮俄,Eden和Survivor比例 。
JVM內(nèi)存為什么要分成新生代尸闸,老年代彻亲,持久代孕锄。新生代中為什么要分為Eden和Survivor。
天貓:
一面:Jvm內(nèi)存模型以及分區(qū)苞尝,需要詳細(xì)到每個區(qū)放什么畸肆。
一面:JVM的內(nèi)存模型,Java8做了什么修改
拼多多:
JVM內(nèi)存分哪幾個區(qū)宙址,每個區(qū)的作用是什么?
美團(tuán):
java內(nèi)存分配
jvm的永久代中會發(fā)生垃圾回收嗎?
一面:jvm內(nèi)存分區(qū)轴脐,為什么要有新生代和老年代?