【JVM】靈性一問——爲什麼用元空間替換永久代?

前言

首先需要明確的是,以下我們討論的HotSpot虛擬機,其他類型的虛擬機,例如JRockit與J9等,壓根就沒有永久代的概念。因此,下面所說的“虛擬機”都是HotSpot版本的。

要想理解這種變化的原因,需要先理解方法區、永久代與元空間的概念與之間的關係。


方法區與永久代,元空間之間的關係

方法區是一種規範,不同的虛擬機廠商可以基於規範做出不同的實現,永久代和元空間就是出於不同jdk版本的實現。

說白了,方法區就像是一個接口,永久代與元空間分別是兩個不同的實現類而已。只不過永久代是這個接口最初的實現類,後來這個接口一直進行變更,直到最後徹底廢棄這個實現類,由新實現類——元空間進行替代。


方法區

借用《深入理解Java虛擬機——JVM高級特性與最佳實踐》中介紹方法區的段落

方法區和堆一樣,是各個線程共享的內存區域,它用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯後的代碼等數據。


Java7及以前版本的永久代的結構

在Java7及以前的版本,是存在永久代的。在Java7版本時,永久代已經發生了悄悄的變化。等到Java8時,徹底廢棄了永久代,由元空間替換。

永久代與堆的構造如下:

(關於堆中的Eden、from與to區域,可以先參考我的另外一篇文章【JVM】說說java中的堆區

從上圖中可以看到,永久代與堆中的老年代是連續的,這裏的連續指的是物理地址連續,永久代本身並不在堆中。因此,老年代與永久代其中一個滿了,都會觸發Full GC。

我們可以使用以下的命令,來顯示指定永久代的大小:

  • -XX:PremSize:設置永久代的初始大小
  • -XX:MaxPermSize: 設置永久代的最大值 

由於方法區主要存儲類的相關信息,所以對於動態生成類的情況比較容易出現永久代的內存溢出。最典型的場景就是,在 jsp 頁面比較多的情況,容易出現永久代內存溢出,會報出"java.lang.OutOfMemoryError: PermGen space "異常。


Java7時,永久代的變化

在Java7時,仍然有永久代,永久代也與堆中的老年代連續,但永久代中存儲的部分數據已經開始轉移到Java Heap或Native Memory中了,比如:

  • 符號引用(Symbols)轉移到了Native Memory
  • 字符串常量池(interned strings)轉移到了Java Heap
  • 類的靜態變量(class statics)轉移到了Java Heap

現在分別在Java6、7、8環境中循環調用String.intern()方法(關於此方法,可以先移步到我的另外一篇文章中【JAVA】String源碼淺談,裏面有對此方法的介紹與實驗),那麼分別會報出以下區域的內存溢出異常

  • Java6時,內存溢出區域爲永久代。因爲在Java6及之前,字符串常量池在永久代中
  • Java7時,內存溢出區域爲堆中。因爲在Java7時,字符串常量池被移到堆中了。
  • Java8時,內存溢出區域仍然爲堆中,不過此時已經沒有永久代了。

Java8開始,永久代就已經消失了,由元空間取而代之。


元空間

元空間(Metaspace),不再與堆連續,而是直接存在於本地內存中,也就是機器的內存。理論上機器內存有多大,元空間的野心就有多大。但可以通過以下的參數來設置元空間的大小:

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

除了上面兩個指定大小的選項以外,還有兩個與 GC 相關的屬性:

  • -XX:MinMetaspaceFreeRatio,在GC之後,最小的Metaspace剩餘空間容量的百分比,減少爲分配空間所導致的垃圾收集
  • -XX:MaxMetaspaceFreeRatio,在GC之後,最大的Metaspace剩餘空間容量的百分比,減少爲釋放空間所導致的垃圾收集

在使用-XX:MaxMetaspaceSize顯示指定元空間的大小爲一個比較小的值,接着在循環中動態加載過多的類,那麼會報出"java.lang.OutOfMemoryError: Metaspace"異常。

在Java8時,仍然使用PremSize或MaxPermSize設置永久代的大小時,會被編譯器忽略並且警告。


Java8中,使用元空間替換永久代的原因

在之前的版本中,字符串常量池存在於永久代中,在大量使用字符串的情況下,非常容易出現OOM的異常。此外,JVM加載的class的總數,方法的大小等都很難確定,因此對永久代大小的指定難以確定。太小的永久代容易導致永久代內存溢出,太大的永久代則容易導致虛擬機內存緊張。

當然,還有很多更多深層次的原因,可以參考這篇博文Metaspace 之一:Metaspace整體介紹(永久代被替換原因、元空間特點、元空間內存查看分析方法)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章