概述
本篇較長钟病,九神帶你從0入門JVM德挣,全文包括包括JVM的分類彻犁、JVM垃圾回收綜述森瘪、JVM的內(nèi)存模型(Java 8)判哥、對(duì)象存活判斷业舍、GC算法撤缴、常見的GC回收器劫扒、GC日志一共七個(gè)部分捐顷。
下面是一張基于Java 8的JVM思維導(dǎo)圖荡陷,如果需要雨效,請(qǐng)關(guān)注公號(hào):“九神說編程”,回復(fù)“JVM”獲取废赞。
JVM的分類
對(duì)于新手徽龟,最大的認(rèn)識(shí)偏差就是覺得只有一種JVM,這個(gè)認(rèn)知是不對(duì)的唉地。SUN公司有對(duì)JVM的規(guī)范据悔,只要符合JVM規(guī)范大家都可以在此基礎(chǔ)上開發(fā)出自己的虛擬機(jī)。事實(shí)上耘沼,我們現(xiàn)在最常用的HotSpot VM就不是SUN公司開發(fā)的极颓。HotSpot VM是對(duì)JVM相關(guān)JSR(Java Specification Requests,Java規(guī)范)的一個(gè)RI(Reference Implementation群嗤,參考實(shí)現(xiàn))菠隆。
關(guān)于相關(guān)的JSR可以在JCP(Java Community Process,Java社區(qū)組織)的官網(wǎng)上尋找狂秘。
如果你想知道你使用的Java虛擬機(jī)的種類骇径,可以在java -version中查詢:
1、HotSpot VM
HotSpot VM是絕對(duì)的主流者春。大家用它的時(shí)候很可能就沒想過還有別的選擇既峡,或者是為了遷就依賴了Oracle/Sun JDK某些具體實(shí)現(xiàn)的爛代碼而選擇用HotSpot VM省點(diǎn)心。
Oracle / Sun JDK碧查、OpenJDK的各種變種(例如IcedTea、Zulu)校仑,用的都是相同核心的HotSpot VM忠售。 從Java SE 7開始,HotSpot VM就是Java規(guī)范的“參考實(shí)現(xiàn)”(RI迄沫,Reference Implementation)稻扬。把它叫做“標(biāo)準(zhǔn)JVM”完全不為過。
當(dāng)在面試中問道“Java性能如何如何”羊瘩、“Java有多少種GC”泰佳、“JVM如何調(diào)優(yōu)”等等,默認(rèn)問的就是特指HotSpot VM尘吗,可見其“主流性”逝她。(其實(shí)這不是件好事,可能有些面試官自己都不知道VM需要分類睬捶,當(dāng)我們討論與JVM相關(guān)的問題是還是要具體到哪個(gè)VM才正確嚴(yán)禁)
JDK8的HotSpot VM已經(jīng)是以前的HotSpot VM與JRockit VM的合并版黔宛,也就是傳說中的“HotRockit”,只是產(chǎn)品里名字還是叫HotSpot VM擒贸。這個(gè)合并并不是要把JRockit的部分代碼插進(jìn)HotSpot里臀晃,而是把前者一些有價(jià)值的功能在后者里重新實(shí)現(xiàn)一遍觉渴。移除PermGen、Java Flight Recorder徽惋、jcmd等都屬于合并項(xiàng)目的一部分案淋。
不過要留意的是,這里我說的HotSpot VM特指“正常配置”版险绘,而不包括“Zero / Shark”版踢京。Wikipedia那個(gè)頁面上把后者稱為“Zero Port”。用這個(gè)版本的人應(yīng)該相當(dāng)少隆圆,很多時(shí)候它的release版在其指定OS上都build不成功漱挚!
下面是抄來的一段與之相關(guān)的歷史:
提起HotSpot VM,相信所有Java程序員都知道渺氧,它是Sun JDK和OpenJDK中所帶的虛擬機(jī)旨涝,也是目前使用范圍最廣的Java虛擬機(jī)。 但不一定所有人都知道的是侣背,這個(gè)目前看起來“血統(tǒng)純正”的虛擬機(jī)在最初并非由Sun公司開發(fā)白华,而是由一家名為“Longview Technologies”的小公司設(shè)計(jì)的; 甚至這個(gè)虛擬機(jī)最初并非是為Java語言而開發(fā)的贩耐,它來源于Strongtalk VM弧腥, 而這款虛擬機(jī)中相當(dāng)多的技術(shù)又是來源于一款支持Self語言實(shí)現(xiàn)“達(dá)到C語言50%以上的執(zhí)行效率”的目標(biāo)而設(shè)計(jì)的虛擬機(jī), Sun公司注意到了這款虛擬機(jī)在JIT編譯上有許多優(yōu)秀的理念和實(shí)際效果潮太,在1997年收購了Longview Technologies公司管搪,從而獲得了HotSpot VM。
HotSpot VM既繼承了Sun之前兩款商用虛擬機(jī)的優(yōu)點(diǎn)(如前面提到的準(zhǔn)確式內(nèi)存管理)铡买,也有許多自己新的技術(shù)優(yōu)勢(shì)更鲁, 如它名稱中的HotSpot指的就是它的熱點(diǎn)代碼探測(cè)技術(shù)(其實(shí)兩個(gè)VM基本上是同時(shí)期的獨(dú)立產(chǎn)品,HotSpot還稍早一些奇钞,HotSpot一開始就是準(zhǔn)確式GC澡为, 而Exact VM之中也有與HotSpot幾乎一樣的熱點(diǎn)探測(cè)。 為了Exact VM和HotSpot VM哪個(gè)成為Sun主要支持的VM產(chǎn)品景埃,在Sun公司內(nèi)部還有過爭(zhēng)論媒至,HotSpot打敗Exact并不能算技術(shù)上的勝利), HotSpot VM的熱點(diǎn)代碼探測(cè)能力可以通過執(zhí)行計(jì)數(shù)器找出最具有編譯價(jià)值的代碼谷徙,然后通知JIT編譯器以方法為單位進(jìn)行編譯拒啰。 如果一個(gè)方法被頻繁調(diào)用,或方法中有效循環(huán)次數(shù)很多完慧,將會(huì)分別觸發(fā)標(biāo)準(zhǔn)編譯和OSR(棧上替換)編譯動(dòng)作图呢。 通過編譯器與解釋器恰當(dāng)?shù)貐f(xié)同工作,可以在最優(yōu)化的程序響應(yīng)時(shí)間與最佳執(zhí)行性能中取得平衡,而且無須等待本地代碼輸出才能執(zhí)行程序蛤织, 即時(shí)編譯的時(shí)間壓力也相對(duì)減小赴叹,這樣有助于引入更多的代碼優(yōu)化技術(shù),輸出質(zhì)量更高的本地代碼指蚜。
在2006年的JavaOne大會(huì)上乞巧,Sun公司宣布最終會(huì)把Java開源,并在隨后的一年摊鸡,陸續(xù)將JDK的各個(gè)部分(其中當(dāng)然也包括了HotSpot VM)在GPL協(xié)議下公開了源碼绽媒, 并在此基礎(chǔ)上建立了OpenJDK。這樣免猾,HotSpot VM便成為了Sun JDK和OpenJDK兩個(gè)實(shí)現(xiàn)極度接近的JDK項(xiàng)目的共同虛擬機(jī)是辕。
在2008年和2009年,Oracle公司分別收購了BEA公司和Sun公司猎提,這樣Oracle就同時(shí)擁有了兩款優(yōu)秀的Java虛擬機(jī):JRockit VM和HotSpot VM获三。 Oracle公司宣布在不久的將來(大約應(yīng)在發(fā)布JDK 8的時(shí)候)會(huì)完成這兩款虛擬機(jī)的整合工作,使之優(yōu)勢(shì)互補(bǔ)锨苏。 整合的方式大致上是在HotSpot的基礎(chǔ)上疙教,移植JRockit的優(yōu)秀特性,譬如使用JRockit的垃圾回收器與MissionControl服務(wù)伞租, 使用HotSpot的JIT編譯器與混合的運(yùn)行時(shí)系統(tǒng)贞谓。
2、J9 VM
J9是IBM開發(fā)的一個(gè)高度模塊化的JVM葵诈,這也是我們?cè)诠ぷ髦锌赡軙?huì)用到的另外一個(gè)VM(除了這兩個(gè)裸弦,在中國的大廠里不大可能接觸到其他的了)。
在許多平臺(tái)上作喘,IBM J9 VM都只能跟IBM產(chǎn)品一起使用理疙。這不是技術(shù)限制,而是許可證限制徊都。例如說在Windows上IBM JDK不是免費(fèi)公開的,而是要跟IBM其它產(chǎn)品一起捆綁發(fā)布的广辰;使用IBM Rational暇矫、IBM WebSphere的話都有機(jī)會(huì)用到J9 VM(也可以自己選擇配置使用別的Java SE JVM)。
根據(jù)許可證择吊,這種捆綁在產(chǎn)品里的J9 VM不應(yīng)該用于運(yùn)行別的Java程序李根,但是大家自己“偷偷的”拿來跑別的程序,IBM也沒力氣管几睛。而在一些IBM的硬件平臺(tái)上房轿,很少客戶是只買硬件不買配套軟件的,IBM給一整套解決方案,里面可能就包括了IBM JDK囱持。這樣自然而然就用上了J9 VM夯接。所以J9 VM得算在主流里,雖然很少是大家主動(dòng)選擇的首選纷妆。
J9 VM的性能水平大致跟HotSpot VM是一個(gè)檔次的盔几。有時(shí)HotSpot快些,有時(shí)J9快些掩幢。不過J9 VM有一些HotSpot VM在JDK8還不支持的功能逊拍,最顯著的一個(gè)就是J9支持AOT編譯和更強(qiáng)大的class data sharing。
3际邻、Sun Classic VM
從名字就可以看出來這是SUN公司開發(fā)的第一款商用的虛擬機(jī)芯丧,現(xiàn)在此款虛擬機(jī)已經(jīng)淘汰了。 這個(gè)虛擬機(jī)只能使用純解釋器的方式來執(zhí)行Java代碼世曾。
4缨恒、Exact VM
這是一款被HotSpot VM給PK掉的VM,上面講歷史的時(shí)候講過度硝。它只存在了很短暫的時(shí)間肿轨,而且只在Solaris平臺(tái)發(fā)布過。
5蕊程、JRockit VM
JRockit VM曾經(jīng)號(hào)稱“世界上速度最快的Java虛擬機(jī)” 椒袍。由于專注于服務(wù)器端應(yīng)用,它可以不太關(guān)注程序啟動(dòng)速度藻茂,因此JRockit內(nèi)部不包含解析器實(shí)現(xiàn)驹暑,全部代碼都靠即時(shí) 編譯器編譯后執(zhí)行。
除此之外辨赐,JRockit的垃圾收集器和MissionControl服務(wù)套件等部分的實(shí)現(xiàn)优俘,在眾多Java虛擬機(jī)中也一直處于領(lǐng)先水平。這一套思想已經(jīng)在Java 8中被HotSpot VM給用上了掀序。
6帆焕、其他
其他的VM還有很多很多,比如Dalvik VM不恭、Microsoft JVM叶雹、Azul VM、Liquid VM换吧、Zing VM等等折晦。但是這些VM和我們實(shí)際中不大會(huì)有交集,也不是歷史沾瓦,更不是HotSpot VM曾經(jīng)的競(jìng)品满着,所以這里就不探討了谦炒。
JVM垃圾回收綜述
為了更快搞明白JVM里面的相關(guān)概念,先綜述JVM的垃圾回收(基于Java 8)风喇。
Hotspot VM的垃圾回收采用“分代回收”的算法宁改。“分代回收”是基于這樣一個(gè)事實(shí):對(duì)象的生命周期不同响驴,所以針對(duì)不同生命周期的對(duì)象可以采取不同的回收方式透且,以便提高回收效率。
Hotspot VM將堆劃分為不同的物理區(qū)豁鲤,就是“分代”思想的體現(xiàn)秽誊。如圖所示,JVM堆主要由新生代琳骡、老年代锅论、元空間構(gòu)成。
1楣号、新生代(Young Generation):大多數(shù)對(duì)象在新生代中被創(chuàng)建最易,其中很多對(duì)象的生命周期很短。每次新生代的垃圾回收(又稱Minor GC)后只有少量對(duì)象存活炫狱,所以選用復(fù)制算法藻懒,只需要少量的復(fù)制成本就可以完成回收。
新生代內(nèi)又分三個(gè)區(qū):一個(gè)Eden區(qū)视译,一般而言有兩個(gè)Survivor區(qū)嬉荆,大部分對(duì)象在Eden區(qū)中生成。當(dāng)Eden區(qū)滿時(shí)酷含,還存活的對(duì)象將被復(fù)制到第一個(gè)Survivor區(qū)鄙早。當(dāng)?shù)谝粋€(gè)Survivor區(qū)滿時(shí),此區(qū)的存活且不滿足“晉升”條件的對(duì)象將被復(fù)制到第一個(gè)Survivor區(qū)椅亚。
對(duì)象每經(jīng)歷一次Minor GC限番,年齡加1,達(dá)到“晉升年齡閾值”后呀舔,被放到老年代弥虐,這個(gè)過程也稱為“晉升”。顯然媚赖,“晉升年齡閾值”的大小直接影響著對(duì)象在新生代中的停留時(shí)間霜瘪,在Serial和ParNew GC兩種回收器中,“晉升年齡閾值”通過參數(shù)MaxTenuringThreshold設(shè)定省古,默認(rèn)值為15粥庄。
2丧失、老年代(Old Generation):在新生代中經(jīng)歷了N次垃圾回收后仍然存活的對(duì)象豺妓,就會(huì)被放到年老代,該區(qū)域中對(duì)象存活率高。老年代的垃圾回收(又稱Major GC)通常使用“標(biāo)記-清理”或“標(biāo)記-整理”算法琳拭。整堆包括新生代和老年代的垃圾回收稱為Full GC(HotSpot VM里训堆,除了CMS之外,其它能收集老年代的GC都會(huì)同時(shí)收集整個(gè)GC堆白嘁,包括新生代)坑鱼。
3、元空間(Metaspace):主要存放元數(shù)據(jù)絮缅,例如類元信息鲁沥、字段、靜態(tài)屬性耕魄、方法画恰、常量,還有運(yùn)行時(shí)常量池等(不含字符串常量)吸奴,與垃圾回收要回收的Java對(duì)象關(guān)系不大允扇。相對(duì)于新生代和年老代來說,該區(qū)域的劃分對(duì)垃圾回收影響很小则奥。
JVM的內(nèi)存模型(Java 8)
JVM內(nèi)存模型分為堆(heap)考润、元空間、棧读处、本地方法棧糊治、程序計(jì)數(shù)器。
JDK8的內(nèi)存模型如下圖:
其中档泽,堆和元空間是線程共享的俊戳,在Java虛擬機(jī)中只有一個(gè)堆、一個(gè)元空間馆匿,并在JVM啟動(dòng)的時(shí)候就創(chuàng)建抑胎,JVM停止才銷毀。棧渐北、本地方法棧阿逃、程序計(jì)數(shù)器是每個(gè)線程私有的,隨著線程的創(chuàng)建而創(chuàng)建赃蛛,隨著線程的結(jié)束而死亡恃锉。
1. 本地方法棧
提供虛擬機(jī)使用到的本地Native方法服務(wù)。
2. 程序計(jì)數(shù)器(Program Counter Register)
程序計(jì)數(shù)器是一塊較小的內(nèi)存空間呕臂。寄存器存儲(chǔ)指令相關(guān)的現(xiàn)場(chǎng)信息破托,由于CPU時(shí)間片輪限制,眾多線程在并發(fā)執(zhí)行過程中歧蒋,任何一個(gè)確定的時(shí)刻土砂,一個(gè)處理器或者多核處理器中的一個(gè)內(nèi)核州既,只會(huì)執(zhí)行某個(gè)線程中的一條指令。這樣必然導(dǎo)致經(jīng)常中斷或恢復(fù)萝映,如何保證分毫無差呢?
每個(gè)線程在創(chuàng)建后吴叶,都會(huì)產(chǎn)生自己的程序計(jì)數(shù)器和棧幀,程序計(jì)數(shù)器用來存放執(zhí)行指令的偏移量和行號(hào)指示器等序臂,線程執(zhí)行或恢復(fù)都要依賴程序計(jì)數(shù)器蚌卤。程序計(jì)數(shù)器在各個(gè)線程之間互不影響,此區(qū)域也不會(huì)發(fā)生內(nèi)存溢出異常奥秆。
特點(diǎn):
一塊較小的內(nèi)存空間
線程私有
是唯一一個(gè)不會(huì)出現(xiàn)OOM的內(nèi)存區(qū)域
3. 棧(Stack)
JVM中的虛擬機(jī)棧是描述Java方法執(zhí)行的內(nèi)存區(qū)域逊彭,它是線程私有的。每個(gè)方法在執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀用于存儲(chǔ)局部變量构订、操作數(shù)棧诫龙、動(dòng)態(tài)鏈接、方法出口等信息鲫咽,每一個(gè)方法被調(diào)用直至執(zhí)行完成的過程签赃,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中從入棧到出棧的過程。
如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度分尸,將會(huì)拋出stackoverflowError通常出現(xiàn)在遞歸方法中锦聊;如果虛擬機(jī)可以動(dòng)態(tài)擴(kuò)展,但是無法申請(qǐng)到足夠的內(nèi)存時(shí)箩绍,就會(huì)拋出outOfMemoryError異常孔庭。
4、堆
Heap存儲(chǔ)著幾乎所有的對(duì)象及數(shù)組材蛛,JVM8中把靜態(tài)變量(字符串常量池)也移到堆區(qū)進(jìn)行存儲(chǔ)圆到。
堆是OOM故障最主要的發(fā)源地,也是是垃圾回收的主要區(qū)域,所以也被稱為GC堆卑吭。通常情況下芽淡,它占用的空間是所有內(nèi)存區(qū)域中最大的,但如果無節(jié)制地創(chuàng)建大量對(duì)象豆赏,也容易消耗完所有的空間挣菲。堆的內(nèi)存空間既可以固定大小,也可運(yùn)行時(shí)動(dòng)態(tài)地調(diào)整掷邦,通過如下參數(shù)設(shè)定初始值和最大值白胀,比如-Xms256M. -Xmx1024M
。其中-X表示它是JVM運(yùn)行參數(shù)抚岗,ms是memorystart初始堆容量的簡(jiǎn)稱 或杠,mx是memory max最大堆容量的簡(jiǎn)稱。但是在通常情況下宣蔚,服務(wù)器在運(yùn)行過程中向抢,堆空間不斷地?cái)U(kuò)容與回縮蔓涧,勢(shì)必形成不必要的系統(tǒng)壓力,所以在線上生產(chǎn)環(huán)境中笋额,JVM的Xms和Xmx設(shè)置成一樣大小,避免在GC后調(diào)整堆大小時(shí)帶來的額外壓力篷扩。
堆分成兩大塊:新生代和老年代兄猩,對(duì)象產(chǎn)生之初在新生代,步入暮年時(shí)進(jìn)入老年代鉴未。新生代又分為1個(gè)Eden區(qū)+ 2個(gè)Survivor區(qū)枢冤,8:1:1的比例。絕大部分對(duì)象在Eden(意為伊甸園)區(qū)生成铜秆,當(dāng)Eden區(qū)裝填滿的時(shí)候淹真,會(huì)觸發(fā)Young GC。垃圾回收的時(shí)候连茧,在Eden區(qū)實(shí)現(xiàn)清除策略核蘸,沒有被引用的對(duì)象則直接回收。依然存活的對(duì)象會(huì)被移送到Survivor(幸存者)區(qū)啸驯,這個(gè)區(qū)真是名副其實(shí)的存在客扎。Survivor 區(qū)分為S0和S1兩塊內(nèi)存空間,送到哪塊空間呢?每次Young GC的時(shí)候罚斗,將存活的對(duì)象復(fù)制到未使用的那塊空間徙鱼,然后將當(dāng)前正在使用的空間完全清除,交換兩塊空間的使用狀態(tài)针姿。
如果Young GC要移送的對(duì)象大于Survivor區(qū)容量上限袱吆,或者超大對(duì)象的閾值超過eden分配擔(dān)保設(shè)置值的上限,則直接移交給老年代.如果老年代也無法放下距淫,則會(huì)觸發(fā)Full Garbage Collection(Full GC)绞绒,如果依然無法放下,則拋OOM.榕暇。
假如一些沒有進(jìn)取心的對(duì)象以為可以一直在新生代的Survivor區(qū)交換來交換去处铛,那就錯(cuò)了。每個(gè)對(duì)象都有一個(gè)計(jì)數(shù)器拐揭,每次Young GC都會(huì)加1撤蟆。-XX:MaxTenuringThreshold
參數(shù)能配置計(jì)數(shù)器的值到達(dá)某個(gè)閾值的時(shí)候,對(duì)象從新生代晉升至老年代堂污。默認(rèn)值是15家肯,可以在Survivor 區(qū)交換14次之后,晉升至老年代盟猖。
堆出現(xiàn)OOM的概率是所有內(nèi)存耗盡異常中最高的讨衣。出錯(cuò)時(shí)的堆內(nèi)信息對(duì)解決問題非常有幫助换棚,所以給JVM設(shè)置運(yùn)行參數(shù)-XX:+HeapDumpOnOutOfMemoryError
,讓JVM遇到OOM異常時(shí)能輸出堆內(nèi)信息反镇,使用-XX:HeapDumpPath
參數(shù)指定dump路徑固蚤。利用JVM參數(shù)-XX:OnOutOfMemoryError
可以在發(fā)生OOM異常時(shí),運(yùn)行一個(gè)本機(jī)的腳本或指令歹茶。
5夕玩、方法區(qū)和持久代
注意,這部分并不存在與Java8惊豺,屬于Java 8之前的東西燎孟。雖然Java 8移除了這部分,但是這里稍微回顧一下尸昧,可供大家比較不同揩页,也防止萬一面試官同學(xué)根本不懂,還在問方法區(qū)和持久代烹俗。
方法區(qū)中存放已經(jīng)被虛擬機(jī)加載的類信息爆侣、常量、靜態(tài)變量幢妄、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)累提。這部分內(nèi)容中的字符串變量在Java8倍丟到了堆里,其他的部分全部丟入元空間磁浇。
方法區(qū)與堆(Java Heap)一樣斋陪,是各個(gè)線程共享的內(nèi)存區(qū)域。雖然Java虛擬機(jī)規(guī)范把方法區(qū)描述為堆的一個(gè)邏輯部分置吓,但是它卻有一個(gè)別名叫做非堆(Non-Heap)无虚。他們的區(qū)別是堆存儲(chǔ)對(duì)象數(shù)據(jù),方法區(qū)存儲(chǔ)靜態(tài)信息衍锚。
持久代友题,即PermGen space的全稱是Permanent Generation space,是指內(nèi)存的永久保存區(qū)域戴质,有人稱之為永久代度宦。需要知道的是,上述的方法區(qū)是JVM的規(guī)范告匠,持久代是HotSpot VM對(duì)這一規(guī)范的實(shí)現(xiàn)戈抄。不同的Java虛擬機(jī)之間可能會(huì)進(jìn)行類共享,因此持久代又分為只讀區(qū)和讀寫區(qū)后专。
JVM用于描述應(yīng)用程序中用到的類和方法的元數(shù)據(jù)也存儲(chǔ)在持久代中划鸽。JVM運(yùn)行時(shí)會(huì)用到多少持久代的空間取決于應(yīng)用程序用到了多少類。除此之外,Java SE庫中的類和方法也都存儲(chǔ)在這里裸诽。我們把所有存儲(chǔ)的內(nèi)容總結(jié)如下:
JVM中類的元數(shù)據(jù)在Java堆中的存儲(chǔ)區(qū)域嫂用。
Java類對(duì)應(yīng)的HotSpot虛擬機(jī)中的內(nèi)部表示也存儲(chǔ)在這里。
類的層級(jí)信息丈冬,字段嘱函,名字。
方法的編譯信息及字節(jié)碼埂蕊。
變量
常量池和符號(hào)解析
JVM 種類有很多往弓,需要注意的是,PermGen space是Hotspot才有粒梦,JRockit以及J9是沒有這個(gè)區(qū)域。
6荸实、元空間
隨著JDK8的到來匀们,JVM不再有PermGen。但類的元數(shù)據(jù)信息(metadata)還在准给,只不過不再是存儲(chǔ)在連續(xù)的堆空間上泄朴,而是移動(dòng)到叫做“Metaspace”的本地內(nèi)存(Native memory)中。
元空間由Klass Metaspace和NoKlass Mestaspace組成露氮,其中:
Klass Metaspace:Klass Metaspace就是用來存klass的祖灰,klass是我們熟知的class文件在jvm里的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu),不過有點(diǎn)要提的是我們看到的類似A.class其實(shí)是存在heap里的畔规,是java.lang.Class的一個(gè)對(duì)象實(shí)例局扶。這塊內(nèi)存是緊接著Heap的,和我們之前的perm一樣叁扫,這塊內(nèi)存大小可通過-XX:CompressedClassSpaceSize參數(shù)來控制三妈,這個(gè)參數(shù)默認(rèn)是1G,但是這塊內(nèi)存也可以沒有莫绣,假如沒有開啟壓縮指針就不會(huì)有這塊內(nèi)存畴蒲,這種情況下klass都會(huì)存在NoKlass Metaspace里,另外如果我們把-Xmx設(shè)置大于32G的話对室,其實(shí)也是沒有這塊內(nèi)存的模燥,因?yàn)闀?huì)這么大內(nèi)存會(huì)關(guān)閉壓縮指針開關(guān)。還有就是這塊內(nèi)存最多只會(huì)存在一塊掩宜。
NoKlass Metaspace:NoKlass Metaspace專門來存klass相關(guān)的其他的內(nèi)容蔫骂,比如method,constantPool等牺汤,這塊內(nèi)存是由多塊內(nèi)存組合起來的纠吴,所以可以認(rèn)為是不連續(xù)的內(nèi)存塊組成的。這塊內(nèi)存是必須的慧瘤,雖然叫做NoKlass Metaspace戴已,但是也其實(shí)可以存klass的內(nèi)容固该。
Klass Metaspace和NoKlass Mestaspace都是所有classloader共享的,所以類加載器們要分配內(nèi)存糖儡,但是每個(gè)類加載器都有一個(gè)SpaceManager伐坏,來管理屬于這個(gè)類加載的內(nèi)存小塊。如果Klass Metaspace用完了握联,那就會(huì)OOM了桦沉,不過一般情況下不會(huì),NoKlass Mestaspace是由一塊塊內(nèi)存慢慢組合起來的金闽,在沒有達(dá)到限制條件的情況下纯露,會(huì)不斷加長這條鏈,讓它可以持續(xù)工作代芜。
元空間的本質(zhì)是對(duì)JVM規(guī)范中方法區(qū)的實(shí)現(xiàn)埠褪。不過元空間與持久代之間最大的區(qū)別在于:元空間并不在虛擬機(jī)中,而是使用本地內(nèi)存挤庇。因此钞速,默認(rèn)情況下,元空間的大小僅受本地內(nèi)存限制嫡秕,但可以通過以下參數(shù)來指定元空間的大锌视铩:
-XX:MetaspaceSize,初始空間大小昆咽,達(dá)到該值就會(huì)觸發(fā)垃圾收集進(jìn)行類型卸載驾凶,同時(shí)GC會(huì)對(duì)該值進(jìn)行調(diào)整:如果釋放了大量的空間,就適當(dāng)降低該值掷酗;如果釋放了很少的空間狭郑,那么在不超過MaxMetaspaceSize時(shí),適當(dāng)提高該值汇在。
-XX:MaxMetaspaceSize翰萨,最大空間,默認(rèn)是沒有限制的糕殉。
除了上面兩個(gè)指定大小的選項(xiàng)以外亩鬼,還有兩個(gè)與 GC 相關(guān)的屬性:
-XX:MinMetaspaceFreeRatio,在GC之后阿蝶,最小的Metaspace剩余空間容量的百分比雳锋,減少為分配空間所導(dǎo)致的垃圾收集
-XX:MaxMetaspaceFreeRatio,在GC之后羡洁,最大的Metaspace剩余空間容量的百分比玷过,減少為釋放空間所導(dǎo)致的垃圾收集
下面講一下元空間的特點(diǎn):
充分利用了Java語言規(guī)范中的好處:類及相關(guān)的元數(shù)據(jù)的生命周期與類加載器的一致。
每個(gè)加載器有專門的存儲(chǔ)空間
只進(jìn)行線性分配
不會(huì)單獨(dú)回收某個(gè)類
省掉了GC掃描及壓縮的時(shí)間
元空間里的對(duì)象的位置是固定的
如果GC發(fā)現(xiàn)某個(gè)類加載器不再存活了,會(huì)把相關(guān)的空間整個(gè)回收掉
最后講一下元空間的內(nèi)存分配模型:
絕大多數(shù)的類元數(shù)據(jù)的空間都從本地內(nèi)存中分配
用來描述類元數(shù)據(jù)的類(klasses)也被刪除了
分元數(shù)據(jù)分配了多個(gè)虛擬內(nèi)存空間
給每個(gè)類加載器分配一個(gè)內(nèi)存塊的列表辛蚊。塊的大小取決于類加載器的類型; sun/反射/代理對(duì)應(yīng)的類加載器的塊會(huì)小一些
歸還內(nèi)存塊粤蝎,釋放內(nèi)存塊列表
一旦元空間的數(shù)據(jù)被清空了,虛擬內(nèi)存的空間會(huì)被回收掉
減少碎片的策略
7袋马、為什么移除持久代
對(duì)于老的Java程序員初澎,我們都會(huì)遇到一個(gè)異常:java.lang.OutOfMemoryError: PermGen space 。說白了就是持久代內(nèi)存溢出虑凛!
為什么會(huì)內(nèi)存溢出呢碑宴?因?yàn)槌志么糜诖娣臗lass和Meta的信息,Class在被 Load的時(shí)候被放入PermGen space區(qū)域桑谍,它和和存放Instance的Heap區(qū)域不同延柠,所以如果你的APP會(huì)LOAD很多CLASS的話,就很可能出現(xiàn)PermGen space錯(cuò)誤。這種錯(cuò)誤常見在web服務(wù)器對(duì)JSP進(jìn)行pre compile的時(shí)候锣披。
而這個(gè)異常實(shí)質(zhì)是暴露了這一設(shè)計(jì)根源的一個(gè)問題:
持久代大小受到-XX:PermSize和-XX:MaxPermSize兩個(gè)參數(shù)的限制贞间,而這兩個(gè)參數(shù)又受到JVM設(shè)定的內(nèi)存大小限制,這就導(dǎo)致在使用中可能會(huì)出現(xiàn)持久代內(nèi)存溢出的問題盈罐。
另外一方面榜跌,為了和JRockit進(jìn)行融合而做的努力闪唆,JRockit用戶并不需要配置持久代盅粪,所以HotSpot也移除了持久代。
根據(jù)上面的內(nèi)外兩方面原因悄蕾,持久代最終被移除票顾,方法區(qū)移至Metaspace,字符串常量移至Java Heap帆调。