1. 程式人生 > >Java虛擬機器(JVM)記憶體的遷移

Java虛擬機器(JVM)記憶體的遷移

對於JVM中方法區,永久代,元空間以及字串常量池的遷移和string.intern方法

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

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

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

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

辭永久代,迎元空間

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

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

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

在這裡插入圖片描述 移除永久代的影響

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

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

元空間記憶體管理

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

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

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

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

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

當常量池中沒有該字串時,JDK7的intern()方法的實現不再是在常量池中建立與此String內容相同的字串,而改為在常量池中記錄java Heap中首次出現的該字串的引用,並返回該引用。