JVM 知識點補充——永久代和元空間

以前已經講過了很多有關 JVM 的內容,今天準備將以前沒有細講的部分進行補充,好比:永久代和元空間。 java

永久代

Java 的內存中有一塊稱之爲方法區的部分,在 JDK8 以前, Hotspot 虛擬機中的實現方式爲永久代(Permanent Generation),別的JVM都沒有這個東西。git

在過去(當自定義類加載器使用不廣泛的時候),類幾乎是「靜態的」而且不多被卸載和回收,所以類也能夠被當作「永久的」。另外因爲類做爲 JVM 實現的一部分,它們不禁程序來建立,由於它們也被認爲是「非堆」的內存。github

永久代是一段連續的內存空間,咱們在 JVM 啓動以前能夠經過設置-XX:MaxPermSize的值來控制永久代的大小,32 位機器默認的永久代的大小爲 64M,64 位的機器則爲 85M。性能

永久代的垃圾回收和老年代的垃圾回收是綁定的,一旦其中一個區域被佔滿,這兩個區都要進行垃圾回收。可是有一個明顯的問題,因爲咱們能夠經過‑XX:MaxPermSize設置永久代的大小,一旦類的元數據超過了設定的大小,程序就會耗盡內存,並出現內存溢出錯誤 (java.lang.OutOfMemoryError: PermGen space)。優化

爲何類的元數據佔用內存會那麼大?由於在 JDK7 以前的 HotSpot 虛擬機中,歸入字符串常量池的字符串被存儲在永久代中,所以致使了一系列的性能問題和內存溢出錯誤。spa

爲了解決這些性能問題,也爲了可以讓 Hotspot 能和其餘的虛擬機同樣管理,元空間就產生了。操作系統

元空間

元空間是 Hotspot 在 JDK8 中新加的內容,其本質和永久代相似,都是對 JVM 規範中方法區的實現。不過元空間與永久代之間最大的區別在於:指針

元空間並不在虛擬機中,而是使用本地內存。所以,默認狀況下,元空間的大小僅受本地內存限制,但能夠經過如下參數來指定元空間的大小:code

-XX:MetaspaceSize cdn

>初始空間大小,達到該值就會觸發垃圾收集進行類型卸載,同時GC會對該值進行調整:若是釋放了大量的空間,就適當下降該值;若是釋放了不多的空間,那麼在不超過MaxMetaspaceSize時,適當提升該值。

  

-XX:MaxMetaspaceSize

>最大空間,默認是沒有限制的。

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

-XX:MinMetaspaceFreeRatio

>在GC以後,最小的Metaspace剩餘空間容量的百分比,減小爲分配空間所致使的垃圾收集

-XX:MaxMetaspaceFreeRatio

>在GC以後,最大的Metaspace剩餘空間容量的百分比,減小爲釋放空間所致使的垃圾收集

移除永久代的影響

因爲類的元數據分配在本地內存中,元空間的最大可分配空間就是系統可用內存空間。所以,咱們就不會遇到永久代存在時的內存溢出錯誤,也不會出現泄漏的數據移到交換區這樣的事情。最終用戶能夠爲元空間設置一個可用空間最大值,若是不進行設置,JVM 會自動根據類的元數據大小動態增長元空間的容量。

注意:永久代的移除並不表明自定義的類加載器泄露問題就解決了。所以,你還必須監控你的內存消耗狀況,由於一旦發生泄漏,會佔用你的大量本地內存,而且還可能致使交換區交換更加糟糕。

元空間內存管理

元空間的內存管理由元空間虛擬機來完成。

先前,對於類的元數據咱們須要不一樣的垃圾回收器進行處理,如今只須要執行元空間虛擬機的 C++ 代碼便可完成。在元空間中,類和其元數據的生命週期和其對應的類加載器是相同的。話句話說,只要類加載器存活,其加載的類的元數據也是存活的,於是不會被回收掉。

準確的來講,每個類加載器的存儲區域都稱做一個元空間,全部的元空間合在一塊兒就是咱們一直說的元空間。當一個類加載器被垃圾回收器標記爲再也不存活,其對應的元空間會被回收。在元空間的回收過程當中沒有重定位和壓縮等操做。可是元空間內的元數據會進行掃描來肯定 Java 引用。

那具體是如何管理的呢?

元空間虛擬機負責元空間的分配,其採用的形式爲組塊分配。組塊的大小因類加載器的類型而異。在元空間虛擬機中存在一個全局的空閒組塊列表。

  1. 當一個類加載器須要組塊時,它就會從這個全局的組塊列表中獲取並維持一個本身的組塊列表。
  2. 當一個類加載器再也不存活時,那麼其持有的組塊將會被釋放,並返回給全局組塊列表。
  3. 類加載器持有的組塊又會被分紅多個塊,每個塊存儲一個單元的元信息。組塊中的塊是線性分配(指針碰撞分配形式)。組塊分配自內存映射區域。這些全局的虛擬內存映射區域以鏈表形式鏈接,一旦某個虛擬內存映射區域清空,這部份內存就會返回給操做系統。

運行時常量池

運行時常量池在 JDK6 及以前版本的 JVM 中是方法區的一部分,而在 HotSpot 虛擬機中方法區的實現是永久代(Permanent Generation)。因此運行時常量池也是在永久代的。

可是 JDK7 及以後版本的 JVM 已經將字符串常量池從方法區中移了出來,在堆(Heap)中開闢了一塊區域存放運行時常量池。

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

存在的問題

前面已經提到,元空間虛擬機採用了組塊分配的形式,同時區塊的大小由類加載器類型決定。類信息並非固定大小,所以有可能分配的空閒區塊和類須要的區塊大小不一樣,這種狀況下可能致使碎片存在。元空間虛擬機目前並不支持壓縮操做,因此碎片化是目前最大的問題。

總結

曾經的永久代,由於容易產生 OOM 而被優化成了元空間,但即使這樣,依然存在着問題,不知道 JDK 以後還會怎樣優化呢?

有興趣的話能夠訪問個人博客或者關注個人公衆號、頭條號,說不定會有意外的驚喜。

death00.github.io/

相關文章
相關標籤/搜索