在過去(當(dāng)自定義類加載器使用不普遍的時(shí)候)点楼,類幾乎是“靜態(tài)的”并且很少被卸載和回收,因此類也可以被看成“永久的”卸夕。另外由于類作為JVM實(shí)現(xiàn)的一部分柱告,它們不由程序來創(chuàng)建截驮,因?yàn)樗鼈円脖徽J(rèn)為是“非堆”的內(nèi)存。
在JDK8之前的HotSpot虛擬機(jī)中末荐,類的這些“永久的”數(shù)據(jù)存放在一個(gè)叫做永久代的區(qū)域侧纯。永久代一段連續(xù)的內(nèi)存空間,我們在JVM啟動(dòng)之前可以通過設(shè)置-XX:MaxPermSize的值來控制永久代的大小甲脏,32位機(jī)器默認(rèn)的永久代的大小為64M眶熬,64位的機(jī)器則為85M妹笆。永久代的垃圾回收和老年代的垃圾回收是綁定的,一旦其中一個(gè)區(qū)域被占滿娜氏,這兩個(gè)區(qū)都要進(jìn)行垃圾回收拳缠。但是有一個(gè)明顯的問題,由于我們可以通過?XX:MaxPermSize 設(shè)置永久代的大小贸弥,一旦類的元數(shù)據(jù)超過了設(shè)定的大小窟坐,程序就會耗盡內(nèi)存,并出現(xiàn)內(nèi)存溢出錯(cuò)誤(OOM)绵疲。
備注:在JDK7之前的HotSpot虛擬機(jī)中哲鸳,納入字符串常量池的字符串被存儲在永久代中,因此導(dǎo)致了一系列的性能問題和內(nèi)存溢出錯(cuò)誤盔憨。
這邊轉(zhuǎn)自? http://www.cnblogs.com/moonandstar08/p/5001914.html
這項(xiàng)改動(dòng)是很有必要的徙菠,因?yàn)閷τ谰么M(jìn)行調(diào)優(yōu)是很困難的。永久代中的元數(shù)據(jù)可能會隨著每一次Full GC發(fā)生而進(jìn)行移動(dòng)郁岩。并且為永久代設(shè)置空間大小也是很難確定的婿奔,因?yàn)檫@其中有很多影響因素,比如類的總數(shù)问慎,常量池的大小和方法數(shù)量等萍摊。
同時(shí),HotSpot虛擬機(jī)的每種類型的垃圾回收器都需要特殊處理永久代中的元數(shù)據(jù)如叼。將元數(shù)據(jù)從永久代剝離出來冰木,不僅實(shí)現(xiàn)了對元空間的無縫管理,還可以簡化Full GC以及對以后的并發(fā)隔離類元數(shù)據(jù)等方面進(jìn)行優(yōu)化薇正。
持久代的空間被徹底地刪除了片酝,它被一個(gè)叫元空間的區(qū)域所替代了囚衔。持久代刪除了之后挖腰,很明顯,JVM會忽略PermSize和MaxPermSize這兩個(gè)參數(shù)练湿,還有就是你再也看不到j(luò)ava.lang.OutOfMemoryError: PermGen error的異常了猴仑。
JDK 8的HotSpot JVM現(xiàn)在使用的是本地內(nèi)存來表示類的元數(shù)據(jù),這個(gè)區(qū)域就叫做元空間肥哎。
元空間的特點(diǎn):
充分利用了Java語言規(guī)范中的好處:類及相關(guān)的元數(shù)據(jù)的生命周期與類加載器的一致辽俗。
每個(gè)加載器有專門的存儲空間
只進(jìn)行線性分配
不會單獨(dú)回收某個(gè)類
省掉了GC掃描及壓縮的時(shí)間
元空間里的對象的位置是固定的
如果GC發(fā)現(xiàn)某個(gè)類加載器不再存活了,會把相關(guān)的空間整個(gè)回收掉
元空間的內(nèi)存分配模型
絕大多數(shù)的類元數(shù)據(jù)的空間都從本地內(nèi)存中分配
用來描述類元數(shù)據(jù)的類也被刪除了
分元數(shù)據(jù)分配了多個(gè)虛擬內(nèi)存空間
給每個(gè)類加載器分配一個(gè)內(nèi)存塊的列表篡诽。塊的大小取決于類加載器的類型; sun/反射/代理對應(yīng)的類加載器的塊會小一些
歸還內(nèi)存塊崖飘,釋放內(nèi)存塊列表
一旦元空間的數(shù)據(jù)被清空了,虛擬內(nèi)存的空間會被回收掉
減少碎片的策略
我們來看下JVM是如何給元數(shù)據(jù)分配虛擬內(nèi)存的空間的
你可以看到虛擬內(nèi)存空間是如何分配的(vs1,vs2,vs3) 杈女,以及類加載器的內(nèi)存塊是如何分配的朱浴。CL是Class Loader的縮寫吊圾。
理解_mark和_klass指針
要想理解下面這張圖,你得搞清楚這些指針都是什么東西翰蠢。
JVM中项乒,每個(gè)對象都有一個(gè)指向它自身類的指針,不過這個(gè)指針只是指向具體的實(shí)現(xiàn)類梁沧,而不是接口或者抽象類檀何。
對于32位的JVM:
_mark : 4字節(jié)常量
_klass: 指向類的4字節(jié)指針 對象的內(nèi)存布局中的第二個(gè)字段( _klass,在32位JVM中廷支,相對對象在內(nèi)存中的位置的偏移量是4频鉴,64位的是8)指向的是內(nèi)存中對象的類定義。
64位的JVM:
_mark : 8字節(jié)常量
_klass: 指向類的8字節(jié)的指針
開啟了指針壓縮的64位JVM: _mark : 8字節(jié)常量
_klass: 指向類的4字節(jié)的指針
Java對象的內(nèi)存布局
類指針壓縮空間(Compressed Class Pointer Space)
只有是64位平臺上啟用了類指針壓縮才會存在這個(gè)區(qū)域恋拍。對于64位平臺砚殿,為了壓縮JVM對象中的_klass指針的大小,引入了類指針壓縮空間(Compressed Class Pointer Space)芝囤。
壓縮指針后的內(nèi)存布局
指針壓縮概要
64位平臺上默認(rèn)打開
使用-XX:+UseCompressedOops壓縮對象指針 "oops"指的是普通對象指針("ordinary" object pointers)似炎。 Java堆中對象指針會被壓縮成32位。 使用堆基地址(如果堆在低26G內(nèi)存中的話悯姊,基地址為0)
使用-XX:+UseCompressedClassPointers選項(xiàng)來壓縮類指針
對象中指向類元數(shù)據(jù)的指針會被壓縮成32位
類指針壓縮空間會有一個(gè)基地址
元空間和類指針壓縮空間的區(qū)別
類指針壓縮空間只包含類的元數(shù)據(jù)羡藐,比如InstanceKlass, ArrayKlass 僅當(dāng)打開了UseCompressedClassPointers選項(xiàng)才生效 為了提高性能,Java中的虛方法表也存放到這里 這里到底存放哪些元數(shù)據(jù)的類型悯许,目前仍在減少
元空間包含類的其它比較大的元數(shù)據(jù)仆嗦,比如方法,字節(jié)碼先壕,常量池等瘩扼。
元空間的調(diào)優(yōu)
使用-XX:MaxMetaspaceSize參數(shù)可以設(shè)置元空間的最大值,默認(rèn)是沒有上限的垃僚,也就是說你的系統(tǒng)內(nèi)存上限是多少它就是多少集绰。-XX:MetaspaceSize選項(xiàng)指定的是元空間的初始大小,如果沒有指定的話谆棺,元空間會根據(jù)應(yīng)用程序運(yùn)行時(shí)的需要?jiǎng)討B(tài)地調(diào)整大小栽燕。
MaxMetaspaceSize的調(diào)優(yōu)
-XX:MaxMetaspaceSize={unlimited}
元空間的大小受限于你機(jī)器的內(nèi)存
限制類的元數(shù)據(jù)使用的內(nèi)存大小,以免出現(xiàn)虛擬內(nèi)存切換以及本地內(nèi)存分配失敗改淑。如果懷疑有類加載器出現(xiàn)泄露碍岔,應(yīng)當(dāng)使用這個(gè)參數(shù);32位機(jī)器上朵夏,如果地址空間可能會被耗盡蔼啦,也應(yīng)當(dāng)設(shè)置這個(gè)參數(shù)。
元空間的初始大小是21M——這是GC的初始的高水位線仰猖,超過這個(gè)大小會進(jìn)行Full GC來進(jìn)行類的回收捏肢。
如果啟動(dòng)后GC過于頻繁掠河,請將該值設(shè)置得大一些
可以設(shè)置成和持久代一樣的大小,以便推遲GC的執(zhí)行時(shí)間
CompressedClassSpaceSize的調(diào)優(yōu)
只有當(dāng)-XX:+UseCompressedClassPointers開啟了才有效
-XX:CompressedClassSpaceSize=1G
由于這個(gè)大小在啟動(dòng)的時(shí)候就固定了的猛计,因此最好設(shè)置得大點(diǎn)唠摹。
沒有使用到的話不要進(jìn)行設(shè)置
JVM后續(xù)可能會讓這個(gè)區(qū)可以動(dòng)態(tài)的增長。不需要是連續(xù)的區(qū)域奉瘤,只要從基地址可達(dá)就行勾拉;可能會將更多的類元信息放回到元空間中;未來會基于PredictedLoadedClassCount的值來自動(dòng)的設(shè)置該空間的大小
元空間的一些工具
jmap -permstat改成了jmap -clstats盗温。它用來打印Java堆的類加載器的統(tǒng)計(jì)數(shù)據(jù)藕赞。對每一個(gè)類加載器,會輸出它的名字卖局,是否存活斧蜕,地址,父類加載器砚偶,以及它已經(jīng)加載的類的數(shù)量及大小批销。除此之外,駐留的字符串(intern)的數(shù)量及大小也會打印出來染坯。
jstat -gc均芽,這個(gè)命令輸出的是元空間的信息而非持久代的
jcmd GC.class_stats提供類元數(shù)據(jù)大小的詳細(xì)信息。使用這個(gè)功能啟動(dòng)程序時(shí)需要加上-XX:+UnlockDiagnosticVMOptions選項(xiàng)单鹿。
提高GC的性能
如果你理解了元空間的概念掀宋,很容易發(fā)現(xiàn)GC的性能得到了提升。
Full GC中仲锄,元數(shù)據(jù)指向元數(shù)據(jù)的那些指針都不用再掃描了劲妙。很多復(fù)雜的元數(shù)據(jù)掃描的代碼(尤其是CMS里面的那些)都刪除了。
元空間只有少量的指針指向Java堆儒喊。這包括:類的元數(shù)據(jù)中指向java/lang/Class實(shí)例的指針;數(shù)組類的元數(shù)據(jù)中镣奋,指向java/lang/Class集合的指針。
沒有元數(shù)據(jù)壓縮的開銷
減少了根對象的掃描(不再掃描虛擬機(jī)里面的已加載類的字典以及其它的內(nèi)部哈希表)
減少了Full GC的時(shí)間
G1回收器中澄惊,并發(fā)標(biāo)記階段完成后可以進(jìn)行類的卸載
java8中metaspace總結(jié)如下:
PermGen 空間的狀況
這部分內(nèi)存空間將全部移除唆途。
JVM的參數(shù):PermSize 和 MaxPermSize 會被忽略并給出警告(如果在啟用時(shí)設(shè)置了這兩個(gè)參數(shù))富雅。
Metaspace 內(nèi)存分配模型
大部分類元數(shù)據(jù)都在本地內(nèi)存中分配掸驱。
用于描述類元數(shù)據(jù)的“klasses”已經(jīng)被移除。
Metaspace 容量
默認(rèn)情況下没佑,類元數(shù)據(jù)只受可用的本地內(nèi)存限制(容量取決于是32位或是64位操作系統(tǒng)的可用虛擬內(nèi)存大斜显簟)。
新參數(shù)(MaxMetaspaceSize)用于限制本地內(nèi)存分配給類元數(shù)據(jù)的大小蛤奢。如果沒有指定這個(gè)參數(shù)鬼癣,元空間會在運(yùn)行時(shí)根據(jù)需要?jiǎng)討B(tài)調(diào)整陶贼。
Metaspace 垃圾回收
對于僵死的類及類加載器的垃圾回收將在元數(shù)據(jù)使用達(dá)到“MaxMetaspaceSize”參數(shù)的設(shè)定值時(shí)進(jìn)行。
適時(shí)地監(jiān)控和調(diào)整元空間對于減小垃圾回收頻率和減少延時(shí)是很有必要的待秃。持續(xù)的元空間垃圾回收說明拜秧,可能存在類、類加載器導(dǎo)致的內(nèi)存泄漏或是大小設(shè)置不合適章郁。
Java 堆內(nèi)存的影響
一些雜項(xiàng)數(shù)據(jù)已經(jīng)移到Java堆空間中枉氮。升級到JDK8之后,會發(fā)現(xiàn)Java堆 空間有所增長暖庄。
Metaspace 監(jiān)控
元空間的使用情況可以從HotSpot1.8的詳細(xì)GC日志輸出中得到聊替。
Jstat 和 JVisualVM兩個(gè)工具,在使用b75版本進(jìn)行測試時(shí)培廓,已經(jīng)更新了惹悄,但是還是能看到老的PermGen空間的出現(xiàn)。
前面已經(jīng)從理論上充分說明肩钠,下面讓我們通過“泄漏”程序進(jìn)行新內(nèi)存空間的觀察……
PermGen vs. Metaspace 運(yùn)行時(shí)比較
為了更好地理解Metaspace內(nèi)存空間的運(yùn)行時(shí)行為泣港,
將進(jìn)行以下幾種場景的測試:
使用JDK1.7運(yùn)行Java程序,監(jiān)控并耗盡默認(rèn)設(shè)定的85MB大小的PermGen內(nèi)存空間价匠。
使用JDK1.8運(yùn)行Java程序爷速,監(jiān)控新Metaspace內(nèi)存空間的動(dòng)態(tài)增長和垃圾回收過程。
使用JDK1.8運(yùn)行Java程序霞怀,模擬耗盡通過“MaxMetaspaceSize”參數(shù)設(shè)定的128MB大小的Metaspace內(nèi)存空間惫东。
首先建立了一個(gè)模擬PermGen OOM的代碼
publicclassClassA?{
publicvoidmethod(String?name)?{
//?do?nothing
}
}
上面是一個(gè)簡單的ClassA,把他編譯成class字節(jié)碼放到D:/classes下面毙石,測試代碼中用URLClassLoader來加載此類型上面類編譯成class
/**
*?模擬PermGen?OOM
*?@author?benhail
*/
publicclassOOMTest?{
publicstaticvoidmain(String[]?args)?{
try{
//準(zhǔn)備url
URL?url?=newFile("D:/classes").toURI().toURL();
URL[]?urls?=?{url};
//獲取有關(guān)類型加載的JMX接口
ClassLoadingMXBean?loadingBean?=?ManagementFactory.getClassLoadingMXBean();
//用于緩存類加載器
List?classLoaders?=newArrayList();
while(true)?{
//加載類型并緩存類加載器實(shí)例
ClassLoader?classLoader?=newURLClassLoader(urls);
classLoaders.add(classLoader);
classLoader.loadClass("ClassA");
//顯示數(shù)量信息(共加載過的類型數(shù)目廉沮,當(dāng)前還有效的類型數(shù)目,已經(jīng)被卸載的類型數(shù)目)
System.out.println("total:?"+?loadingBean.getTotalLoadedClassCount());
System.out.println("active:?"+?loadingBean.getLoadedClassCount());
System.out.println("unloaded:?"+?loadingBean.getUnloadedClassCount());
}
}catch(Exception?e)?{
e.printStackTrace();
}
}
}
虛擬機(jī)器參數(shù)設(shè)置如下:-verbose -verbose:gc
設(shè)置-verbose參數(shù)是為了獲取類型加載和卸載的信息
設(shè)置-verbose:gc是為了獲取垃圾收集的相關(guān)信息
JDK 1.7 @64-bit – PermGen 耗盡測試
Java1.7的PermGen默認(rèn)空間為85 MB(或者可以通過-XX:MaxPermSize=XXXm指定)
可以從上面的JVisualVM的截圖看出:當(dāng)加載超過6萬個(gè)類之后徐矩,PermGen被耗盡滞时。我們也能通過程序和GC的輸出觀察耗盡的過程。
程序輸出(摘取了部分)
......
[Loaded?ClassA?from?file:/D:/classes/]
total:64887
active:64887
unloaded:0
[GC?245041K->213978K(536768K),0.0597188secs]
[Full?GC?213978K->211425K(644992K),0.6456638secs]
[GC?211425K->211425K(656448K),0.0086696secs]
[Full?GC?211425K->211411K(731008K),0.6924754secs]
[GC?211411K->211411K(726528K),0.0088992secs]
...............
java.lang.OutOfMemoryError:?PermGen?space
JDK 1.8 @64-bit – Metaspace大小動(dòng)態(tài)調(diào)整測試
Java的Metaspace空間:不受限制 (默認(rèn))
從上面的截圖可以看到滤灯,JVM Metaspace進(jìn)行了動(dòng)態(tài)擴(kuò)展坪稽,本地內(nèi)存的使用由20MB增長到646MB,以滿足程序中不斷增長的類數(shù)據(jù)內(nèi)存占用需求鳞骤。我們也能觀察到JVM的垃圾回收事件—試圖銷毀僵死的類或類加載器對象窒百。但是,由于我們程序的泄漏豫尽,JVM別無選擇只能動(dòng)態(tài)擴(kuò)展Metaspace內(nèi)存空間篙梢。程序加載超過10萬個(gè)類,而沒有出現(xiàn)OOM事件美旧。
JDK 1.8 @64-bit – Metaspace 受限測試
Java的Metaspace空間:128MB(-XX:MaxMetaspaceSize=128m)
可以從上面的JVisualVM的截圖看出:當(dāng)加載超過2萬個(gè)類之后渤滞,Metaspace被耗盡贬墩;與JDK1.7運(yùn)行時(shí)非常相似。我們也能通過程序和GC的輸出觀察耗盡的過程妄呕。另一個(gè)有趣的現(xiàn)象是陶舞,保留的原生內(nèi)存占用量是設(shè)定的最大大小兩倍之多。這可能表明绪励,如果可能的話吊说,可微調(diào)元空間容量大小策略,來避免本地內(nèi)存的浪費(fèi)优炬。
從Java程序的輸出中看到如下異常颁井。
[Loaded?ClassA?from?file:/D:/classes/]
total:21393
active:21393
unloaded:0
[GC?(Metadata?GC?Threshold)?64306K->57010K(111616K),0.0145502secs]
[Full?GC?(Metadata?GC?Threshold)?57010K->56810K(122368K),0.1068084secs]
java.lang.OutOfMemoryError:?Metaspace
在設(shè)置了MaxMetaspaceSize的情況下,該空間的內(nèi)存仍然會耗盡蠢护,進(jìn)而引發(fā)“java.lang.OutOfMemoryError: Metadata space”錯(cuò)誤雅宾。因?yàn)轭惣虞d器的泄漏仍然存在,而通常Java又不希望無限制地消耗本機(jī)內(nèi)存葵硕,因此設(shè)置一個(gè)類似于MaxPermSize的限制看起來也是合理的眉抬。
總結(jié)
之前不管是不是需要,JVM都會吃掉那塊空間……如果設(shè)置得太小懈凹,JVM會死掉蜀变;如果設(shè)置得太大,這塊內(nèi)存就被JVM浪費(fèi)了介评。理論上說库北,現(xiàn)在你完全可以不關(guān)注這個(gè),因?yàn)镴VM會在運(yùn)行時(shí)自動(dòng)調(diào)校為“合適的大小”们陆;
提高Full GC的性能寒瓦,在Full GC期間,Metadata到Metadata pointers之間不需要掃描了坪仇,別小看這幾納秒時(shí)間杂腰;
隱患就是如果程序存在內(nèi)存泄露,像OOMTest那樣椅文,不停的擴(kuò)展metaspace的空間喂很,會導(dǎo)致機(jī)器的內(nèi)存不足,所以還是要有必要的調(diào)試和監(jiān)控皆刺。
總結(jié)
Hotspot中的元數(shù)據(jù)現(xiàn)在存儲到了元空間里少辣。mmap中的內(nèi)存塊的生命周期與類加載器的一致。
類指針壓縮空間(Compressed class pointer space)目前仍然是固定大小的芹橡,但它的空間較大
可以進(jìn)行參數(shù)的調(diào)優(yōu)毒坛,不過這不是必需的。
未來可能會增加其它的優(yōu)化及新特性林说。比如煎殷, 應(yīng)用程序類數(shù)據(jù)共享;新生代GC優(yōu)化腿箩,G1回收器進(jìn)行類的回收豪直;減少元數(shù)據(jù)的大小,以及JVM內(nèi)部對象的內(nèi)存占用量珠移。