1. 程式人生 > >從永久代(PermGen)到元空間(Metaspace)

從永久代(PermGen)到元空間(Metaspace)

為什麼要做這個轉換?總結以下幾點原因:

1、字串存在永久代中,容易出現效能問題和記憶體溢位。

2、類及方法的資訊等比較難確定其大小,因此對於永久代的大小指定比較困難,太小容易出現永久代溢位,太大則容易導致老年代溢位。

3、永久代會為 GC 帶來不必要的複雜度,並且回收效率偏低。

4、Oracle 可能會將HotSpot 與 JRockit 合二為一。

名詞概念:
元空間: Metaspace

1,大部分類元資料都在本地記憶體中分配。

2,預設情況下,類元資料只受可用的本地記憶體限制(容量取決於是32/64位作業系統的可用虛擬記憶體大小)。

3,新引數(MaxMetaspaceSize)用於限制本地記憶體分配給類元資料的大小。如果沒有指定這個引數,元空間會在執行時根據需要動態調整。

4,對於將死的類及類載入器的垃圾回收將在元資料使用達到“MaxMetaspaceSize”引數的設定值時進行。

5,實時地監控和調整元空間對於減小垃圾回收頻率和減少延時是很有必要的。持續的元空間垃圾回收說明,可能存在類、類載入器導致的記憶體洩漏或是大小設定不合適。

永久代: PermGen

1, 這部分記憶體空間將全部移除。
2, JVM的引數:PermSize 和 MaxPermSize 會被忽略並給出警告(如果在啟用時設定了這兩個引數)。

元空間的特點:

1,每個載入器有專門的儲存空間。
2,不會單獨回收某個類。
3,元空間裡的物件的位置是固定的。
4,如果發現某個載入器不再存貨了,會把相關的空間整個回收。

詳情介紹:

隨著JDK8的到來,JVM不再有PermGen。但類的元資料資訊(metadata)還在,只不過不再是儲存在連續的堆空間上,而是移動到叫做“Metaspace”的本地記憶體(Native memory)中。

類的元資料資訊轉移到Metaspace的原因是PermGen很難調整。PermGen中類的元資料資訊在每次FullGC的時候可能會被收集,但成績很難令人滿意。而且應該為PermGen分配多大的空間很難確定,因為PermSize的大小依賴於很多因素,比如JVM載入的class的總數,常量池的大小,方法的大小等。

此外,在HotSpot中的每個垃圾收集器需要專門的程式碼來處理儲存在PermGen中的類的元資料資訊。從PermGen分離類的元資料資訊到Metaspace,由於Metaspace的分配具有和Java Heap相同的地址空間,因此Metaspace和Java Heap可以無縫的管理,而且簡化了FullGC的過程,以至將來可以並行的對元資料資訊進行垃圾收集,而沒有GC暫停。

永久代的移除對終端使用者意味著什麼?

由於類的元資料可以在本地記憶體(native memory)之外分配,所以其最大可利用空間是整個系統記憶體的可用空間。這樣,你將不再會遇到OOM錯誤,溢位的記憶體會湧入到交換空間。終端使用者可以為類元資料指定最大可利用的本地記憶體空間,JVM也可以增加本地記憶體空間來滿足類元資料資訊的儲存。

注:永久代的移除並不意味者類載入器洩露的問題就沒有了。因此,你仍然需要監控你的消費和計劃,因為記憶體洩露會耗盡整個本地記憶體,導致記憶體交換(swapping),這樣只會變得更糟。

移動到Metaspace和它的記憶體分配

Metaspace VM利用記憶體管理技術來管理Metaspace。這使得由不同的垃圾收集器來處理類元資料的工作,現在僅僅由Metaspace VM在Metaspace中通過C++來進行管理。Metaspace背後的一個思想是,類和它的元資料的生命週期是和它的類載入器的生命週期一致的。也就是說,只要類的類載入器是存活的,在Metaspace中的類元資料也是存活的,不能被釋放。

類和它的元資料的生命週期 = 類載入器的生命週期

之前我們不嚴格的使用這個術語“Metaspace”。更正式的,每個類載入器儲存區叫做“a metaspace”。這些metaspaces一起總體稱為”the Metaspace”。僅僅當類載入器不在存活,被垃圾收集器宣告死亡後,該類載入器對應的metaspace空間才可以回收。Metaspace空間沒有遷移和壓縮。但是元資料會被掃描是否存在Java引用。

僅僅當類載入器不在存活,被垃圾收集器宣告死亡後,該類載入器對應的metaspace空間才可以回收。

Metaspace VM使用一個塊分配器(chunking allocator)來管理Metaspace空間的記憶體分配。塊的大小依賴於類載入器的型別。其中有一個全域性的可使用的塊列表(a global free list of chunks)。當類載入器需要一個塊的時候,類載入器從全域性塊列表中取出一個塊,新增到它自己維護的塊列表中。當類載入器死亡,它的塊將會被釋放,歸還給全域性的塊列表。塊(chunk)會進一步被劃分成blocks,每個block儲存一個元資料單元(a unit of metadata)。Chunk中Blocks的分配線性的(pointer bump)。這些chunks被分配在記憶體對映空間(memory mapped(mmapped) spaces)之外。在一個全域性的虛擬記憶體對映空間(global virtual mmapped spaces)的連結串列,當任何虛擬空間變為空時,就將該虛擬空間歸還回作業系統。[圖片上傳失敗...(image-c09518-1524470109458)]

 

上面這幅圖展示了Metaspace使用metachunks在mmapeded virual spaces分配的情形。類載入器1和3描述的是反射或匿名類載入器,使用“特定的”chunk尺寸。類載入器2和4使用小還是中等的chunk尺寸取決於載入的類數量。

Metaspace大小的調整和可以使用的工具

正如前面提到了,Metaspace VM管理Metaspace空間的增長。但有時你會想通過在命令列顯示的設定引數-XX:MaxMetaspaceSize來限制Metaspace空間的增長。預設情況下,-XX:MaxMetaspaceSize並沒有限制,因此,在技術上,Metaspace的尺寸可以增長到交換空間,而你的本地記憶體分配將會失敗。

對於64位的伺服器端JVM,-XX:MetaspaceSize的預設大小是21M。這是初始的限制值(the initial high watermark)。一旦達到這個限制值,FullGC將會被觸發進行類解除安裝(當這些類的類載入器不再存活時),然後這個high watermark被重置。新的high watermark的值依賴於空餘Metaspace的容量。如果沒有足夠的空間被釋放,high watermark的值將會上升;如果釋放了大量的空間,那麼high watermark的值將會下降。如果初始的watermark設定的太低,這個過程將會進行多次。你可以通過垃圾收集日誌來顯示的檢視這個垃圾收集的過程。所以,一般建議在命令列設定一個較大的值給XX:MetaspaceSize來避免初始時的垃圾收集。

每次垃圾收集之後,Metaspace VM會自動的調整high watermark,推遲下一次對Metaspace的垃圾收集。

這兩個引數,-XX:MinMetaspaceFreeRatio和-XX:MaxMetaspaceFreeRatio,類似於GC的FreeRatio引數,可以放在命令列。

名詞概念:

元空間: Metaspace

1,大部分類元資料都在本地記憶體中分配。

2,預設情況下,類元資料只受可用的本地記憶體限制(容量取決於是32/64位作業系統的可用虛擬記憶體大小)。

3,新引數(MaxMetaspaceSize)用於限制本地記憶體分配給類元資料的大小。如果沒有指定這個引數,元空間會在執行時根據需要動態調整。

4,對於將死的類及類載入器的垃圾回收將在元資料使用達到“MaxMetaspaceSize”引數的設定值時進行。

5,實時地監控和調整元空間對於減小垃圾回收頻率和減少延時是很有必要的。持續的元空間垃圾回收說明,可能存在類、類載入器導致的記憶體洩漏或是大小設定不合適。

永久代: PermGen

1, 這部分記憶體空間將全部移除。
2, JVM的引數:PermSize 和 MaxPermSize 會被忽略並給出警告(如果在啟用時設定了這兩個引數)。

元空間的特點:

1,每個載入器有專門的儲存空間。
2,不會單獨回收某個類。
3,元空間裡的物件的位置是固定的。
4,如果發現某個載入器不再存貨了,會把相關的空間整個回收。

正如大家所知,JDK 8 Early Access版已經提供下載。這使開發者可以體驗Java8的新特性。其中之一,是Oracle從JDK7釋出以來就一直宣稱的要完全移除永久代空間。例如,字串內部池,已經在JDK7中從永久代中移除。JDK8的釋出將宣告它的終結。

Java 堆記憶體的影響

  • 一些雜項資料已經移到Java堆空間中。升級到JDK8之後,會發現Java堆 空間有所增長。

Metaspace 監控

  • 元空間的使用情況可以從HotSpot1.8的詳細GC日誌輸出中得到。
  • Jstat 和 JVisualVM兩個工具,在我們使用b75版本進行測試時,已經更新了,但是還是能看到老的PermGen空間的出現。

其實,移除永久代的工作從JDK1.7就開始了。字串內部池,已經在JDK7中從永久代中移除,JDK1.7中,儲存在永久代的部分資料就已經轉移到了Java Heap或者是 Native Heap。但永久代仍存在於JDK1.7中,並沒完全移除,譬如符號引用(Symbols)轉移到了native heap;字面量(interned strings)轉移到了java heap;類的靜態變數(class statics)轉移到了java heap。我們可以通過一段程式來比較 JDK 1.6 與 JDK 1.7及 JDK 1.8 的區別,以字串常量為例: