1. 程式人生 > 實用技巧 >JVM 1.8 永久代---元空間 的變動

JVM 1.8 永久代---元空間 的變動

JDK8 HotSpot JVM 將移除永久區,使用本地記憶體來儲存類元資料資訊並稱之為:元空間(Metaspace)

以下是JVM記憶體模型中方法區的變動

1.新生代:Eden+From Survivor+To Survivor

2.老年代:OldGen

3.永久代(方法區的實現) : PermGen----->替換為Metaspace(本地記憶體中)

方法區和“PermGen space”又有著本質的區別。前者是 JVM 的規範,而後者則是 JVM 規範的一種實現,並且只有 HotSpot 才有“PermGen space”,而對於其他型別的虛擬機器,如 JRockit(Oracle)、J9(IBM) 並沒有“PermGen space”。由於方法區主要儲存類的相關資訊,所以對於動態生成類的情況比較容易出現永久代的記憶體溢位

元空間的本質和永久代類似,都是對JVM規範中方法區的實現。不過元空間與永久代之間最大的區別在於:元空間並不在虛擬機器中,而是使用本地記憶體。因此,預設情況下,元空間的大小僅受本地記憶體限制,但可以通過以下引數來指定元空間的大小:

-XX:MetaspaceSize,初始空間大小,達到該值就會觸發垃圾收集進行型別解除安裝,同時GC會對該值進行調整:如果釋放了大量的空間,就適當降低該值;如果釋放了很少的空間,那麼在不超過MaxMetaspaceSize時,適當提高該值。
  -XX:MaxMetaspaceSize,最大空間,預設是沒有限制的。

 除了上面兩個指定大小的選項以外,還有兩個與 GC 相關的屬性:
  
-XX:MinMetaspaceFreeRatio,在GC之後,最小的Metaspace剩餘空間容量的百分比,減少為分配空間所導致的垃圾收集   -XX:MaxMetaspaceFreeRatio,在GC之後,最大的Metaspace剩餘空間容量的百分比,減少為釋放空間所導致的垃圾收集

《Java虛擬機器規範(JavaSE7)》中也說了方法區是堆的邏輯組成部分。
實際上JDK1.7中,儲存在永久代的部分資料就已經轉移到了Java Heap或者是Native Heap。但永久代在JDK1.8才被移除

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

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

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

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

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

執行時常量池在JDK1.6及之前版本的JVM中是方法區的一部分,而在HotSpot虛擬機器中方法區放在了”永久代(Permanent Generation)”。所以執行時常量池也是在永久代的。
但是JDK1.7及之後版本的JVM已經將字串常量池從方法區中移了出來,在Java 堆(Heap)中開闢了一塊區域存放字串常量池。

String.intern()是一個Native方法,它的作用是:如果執行時常量池中已經包含一個等於此String物件內容的字串,則返回常量池中該字串的引用;如果沒有,則在常量池中建立與此String內容相同的字串,並返回常量池中建立的字串的引用。

變動原因:

  • 字串存在永久代中,現實使用中易出問題, 由於永久代記憶體經常不夠用或發生記憶體洩露,爆出異常 java.lang.OutOfMemoryError: PermGen
  • 類及方法的資訊等比較難確定其大小,因此對於永久代的大小指定比較困難,太小容易出現永久代溢位,太大則容易導致老年代溢位。
  • 永久代會為 GC 帶來不必要的複雜度,並且回收效率偏低。

This is part of the JRockit and Hotspot convergence effort. JRockit customers do not need to configure the permanent generation (since JRockit does not have a permanent generation) and are accustomed to not configuring the permanent generation.
即:移除永久代是為融合HotSpot JVM與 JRockit VM而做出的努力,因為JRockit沒有永久代,不需要配置永久代。