Java永久代去哪兒了

m53469發表於2021-09-09


Java永久代去哪兒了

在Java虛擬機器(以下簡稱JVM)中,類包含其對應的後設資料,比如類的層級資訊,方法資料和方法資訊(如位元組碼,棧和變數大小),執行時常量池,已確定的符號引用和虛方法表。

在過去(當自定義類載入器使用不普遍的時候),類幾乎是“靜態的”並且很少被解除安裝和回收,因此類也可以被看成“永久的”。另外由於類作為JVM實現的一部分,它們不由程式來建立,因為它們也被認為是“非堆”的記憶體。

在JDK8之前的HotSpot虛擬機器中,類的這些“永久的”資料存放在一個叫做永久代的區域。永久代一段連續的記憶體空間,我們在JVM啟動之前可以透過設定-XX:MaxPermSize的值來控制永久代的大小,32位機器預設的永久代的大小為64M,64位的機器則為85M。永久代的垃圾回收和老年代的垃圾回收是繫結的,一旦其中一個區域被佔滿,這兩個區都要進行垃圾回收。但是有一個明顯的問題,由於我們可以透過‑XX:MaxPermSize 設定永久代的大小,一旦類的後設資料超過了設定的大小,程式就會耗盡記憶體,並出現記憶體溢位錯誤(OOM)。

備註:在JDK7之前的HotSpot虛擬機器中,納入字串常量池的字串被儲存在永久代中,因此導致了一系列的效能問題和記憶體溢位錯誤。想要了解這些永久代移除這些字串的資訊,請訪問這裡檢視。

歡迎工作一到五年的Java工程師朋友們加入Java技術交流:585550789

群內提供免費的Java架構學習資料(裡面有高可用、高併發、高效能及分散式、Jvm效能調優、Spring原始碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)合理利用自己每一分每一秒的時間來學習提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個交代!

辭永久代,迎元空間

隨著Java8的到來,我們再也見不到永久代了。但是這並不意味著類的後設資料資訊也消失了。這些資料被移到了一個與堆不相連的本地記憶體區域,這個區域就是我們要提到的元空間。

這項改動是很有必要的,因為對永久代進行調優是很困難的。永久代中的後設資料可能會隨著每一次Full GC發生而進行移動。並且為永久代設定空間大小也是很難確定的,因為這其中有很多影響因素,比如類的總數,常量池的大小和方法數量等。

同時,HotSpot虛擬機器的每種型別的垃圾回收器都需要特殊處理永久代中的後設資料。將後設資料從永久代剝離出來,不僅實現了對元空間的無縫管理,還可以簡化Full GC以及對以後的併發隔離類後設資料等方面進行最佳化。

Java永久代去哪兒了

移除永久代的影響

由於類的後設資料分配在本地記憶體中,元空間的最大可分配空間就是系統可用記憶體空間。因此,我們就不會遇到永久代存在時的記憶體溢位錯誤,也不會出現洩漏的資料移到交換區這樣的事情。終端使用者可以為元空間設定一個可用空間最大值,如果不進行設定,JVM會自動根據類的後設資料大小動態增加元空間的容量。

注意:永久代的移除並不代表自定義的類載入器洩露問題就解決了。因此,你還必須監控你的記憶體消耗情況,因為一旦發生洩漏,會佔用你的大量本地記憶體,並且還可能導致交換區交換更加糟糕。

元空間記憶體管理

元空間的記憶體管理由元空間虛擬機器來完成。先前,對於類的後設資料我們需要不同的垃圾回收器進行處理,現在只需要執行元空間虛擬機器的C++程式碼即可完成。在元空間中,類和其後設資料的生命週期和其對應的類載入器是相同的。話句話說,只要類載入器存活,其載入的類的後設資料也是存活的,因而不會被回收掉。

我們從行文到現在提到的元空間稍微有點不嚴謹。準確的來說,每一個類載入器的儲存區域都稱作一個元空間,所有的元空間合在一起就是我們一直說的元空間。當一個類載入器被垃圾回收器標記為不再存活,其對應的元空間會被回收。在元空間的回收過程中沒有重定位和壓縮等操作。但是元空間內的後設資料會進行掃描來確定Java引用。

元空間虛擬機器負責元空間的分配,其採用的形式為組塊分配。組塊的大小因類載入器的型別而異。在元空間虛擬機器中存在一個全域性的空閒組塊列表。當一個類載入器需要組塊時,它就會從這個全域性的組塊列表中獲取並維持一個自己的組塊列表。當一個類載入器不再存活,那麼其持有的組塊將會被釋放,並返回給全域性組塊列表。類載入器持有的組塊又會被分成多個塊,每一個塊儲存一個單元的元資訊。組塊中的塊是線性分配(指標碰撞分配形式)。組塊分配自記憶體對映區域。這些全域性的虛擬記憶體對映區域以連結串列形式連線,一旦某個虛擬記憶體對映區域清空,這部分記憶體就會返回給作業系統。

Java永久代去哪兒了

上圖展示的是虛擬記憶體對映區域如何進行元組塊的分配。類載入器1和3表明使用了反射或者為匿名類載入器,他們使用了特定大小組塊。 而類載入器2和4根據其內部條目的數量使用小型或者中型的組塊。

元空間調優與工具

正如上面提到的,元空間虛擬機器控制元空間的增長。但是有些時候我們想限制其增長,比如透過顯式在命令列中設定-XX:MaxMetaspaceSize。預設情況下,-XX:MaxMetaspaceSize的值沒有限制,因此元空間甚至可以延伸到交換區,但是這時候當我們進行本地記憶體分配時將會失敗。

對於一個64位的伺服器端JVM來說,其預設的–XX:MetaspaceSize值為21MB。這就是初始的高水位線。一旦觸及到這個水位線,Full GC將會被觸發並解除安裝沒有用的類(即這些類對應的類載入器不再存活),然後這個高水位線將會重置。新的高水位線的值取決於GC後釋放了多少元空間。如果釋放的空間不足,這個高水位線則上升。如果釋放空間過多,則高水位線下降。如果初始化的高水位線設定過低,上述高水位線調整情況會發生很多次。透過垃圾回收器的日誌我們可以觀察到Full GC多次呼叫。為了避免頻繁的GC,建議將–XX:MetaspaceSize設定為一個相對較高的值。

經過多次GC之後,元空間虛擬機器自動調節高水位線,以此來推遲下一次垃圾回收到來。

有這樣兩個選項 ‑XX:MinMetaspaceFreeRatio和‑XX:MaxMetaspaceFreeRatio,他們類似於GC的FreeRatio選項,用來設定元空間空閒比例的最大值和最小值。我們可以透過命令列對這兩個選項設定對應的值。

下面是一些改進的工具,用來獲取更多關於元空間的資訊。

jmap -clstats PID 列印類載入器資料。(-clstats是-permstat的替代方案,在JDK8之前,-permstat用來列印類載入器的資料)。

jstat -gc LVMID 用來列印元空間的資訊。具體內容如下:

Java永久代去哪兒了

jcmd PID GC.class_stats 一個新的診斷命令,用來連線到執行的JVM並輸出詳盡的類後設資料的柱狀圖。

存在的問題

前面已經提到,元空間虛擬機器採用了組塊分配的形式,同時區塊的大小由類載入器型別決定。類資訊並不是固定大小,因此有可能分配的空閒區塊和類需要的區塊大小不同,這種情況下可能導致碎片存在。元空間虛擬機器目前並不支援壓縮操作,所以碎片化是目前最大的問題。

Java永久代去哪兒了

喜歡小編輕輕點個關注吧!

©著作權歸作者所有:來自51CTO部落格作者java1856905的原創作品,如需轉載,請註明出處,否則將追究法律責任


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2819/viewspace-2819461/,如需轉載,請註明出處,否則將追究法律責任。

相關文章