HotSpot虛擬機(jī)在java 1.8中的新實(shí)現(xiàn)

HotSpot

Java HotSpot 虛擬機(jī)是 Java SE 平臺(tái)的一個(gè)核心組件。它實(shí)現(xiàn) Java 虛擬機(jī)規(guī)范,并作為 Java 運(yùn)行時(shí)環(huán)境中的一個(gè)共享庫來提供。作為 Java 字節(jié)碼執(zhí)行引擎,它在多種操作系統(tǒng)和架構(gòu)上提供 Java 運(yùn)行時(shí)設(shè)施,如線程和對(duì)象同步。它包括自適應(yīng)將 Java 字節(jié)碼編譯成優(yōu)化機(jī)器指令的動(dòng)態(tài)編譯器,并使用為降低暫停時(shí)間和吞吐量而優(yōu)化的垃圾收集器來高效管理 Java 堆。它為分析、監(jiān)視和調(diào)試工具及應(yīng)用程序提供數(shù)據(jù)和信息。

最近在研究它在1.8中的新特性:

  • JAVA 從永久區(qū)(PermGen)到元空間(Metaspace)

JAVA 從永久區(qū)(PermGen)到元空間(Metaspace)

1. 永久代

在說java8內(nèi)存模型之前先說一下永久代的概念。

在Java虛擬機(jī)(JVM)內(nèi)部,class文件中包括類的版本、字段、方法、接口等描述信息,還有運(yùn)行時(shí)常量池,用于存放編譯器生成的各種字面量和符號(hào)引用。

在過去類大多是”static”的,很少被卸載或收集,因此被稱為“永久的(Permanent)”。

同時(shí),由于類class是JVM實(shí)現(xiàn)的一部分,并不是由應(yīng)用創(chuàng)建的,所以又被認(rèn)為是“非堆(non-heap)”內(nèi)存。

在JDK8之前的HotSpot JVM,存放這些”永久的”的區(qū)域叫做“永久代(permanent generation)”。

永久代是一片連續(xù)的堆空間,在JVM啟動(dòng)之前通過在命令行設(shè)置參數(shù)-XX:MaxPermSize來設(shè)定永久代最大可分配的內(nèi)存空間,默認(rèn)大小是64M(64位JVM由于指針膨脹,默認(rèn)是85M)。

永久代的垃圾收集是和老年代(old generation)捆綁在一起的,因此無論誰滿了,都會(huì)觸發(fā)永久代和老年代的垃圾收集。

不過,一個(gè)明顯的問題是,當(dāng)JVM加載的類信息容量超過了參數(shù)-XX:MaxPermSize設(shè)定的值時(shí),應(yīng)用將會(huì)報(bào)OOM的錯(cuò)誤

2. Metaspace(元空間)

jdk1.8中則把永久代給完全刪除了,取而代之的是 MetaSpace


heap in JAVA 8

2.1 metaspace的組成

metaspace其實(shí)由兩大部分組成

  • Klass Metaspace
  • NoKlass Metaspace

Klass Metaspace就是用來存klass的,klass是我們熟知的class文件在jvm里的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu),不過有點(diǎn)要提的是我們看到的類似A.class其實(shí)是存在heap里的,是java.lang.Class的一個(gè)對(duì)象實(shí)例。

這塊內(nèi)存是緊接著Heap的,和我們之前的perm一樣,這塊內(nèi)存大小可通過-XX:CompressedClassSpaceSize參數(shù)來控制,這個(gè)參數(shù)前面提到了默認(rèn)是1G,但是這塊內(nèi)存也可以沒有,假如沒有開啟壓縮指針就不會(huì)有這塊內(nèi)存,這種情況下klass都會(huì)存在NoKlass Metaspace里,另外如果我們把-Xmx設(shè)置大于32G的話,其實(shí)也是沒有這塊內(nèi)存的,因?yàn)檫@么大內(nèi)存會(huì)關(guān)閉壓縮指針開關(guān)。

還有就是這塊內(nèi)存最多只會(huì)存在一塊。

NoKlass Metaspace專門來存klass相關(guān)的其他的內(nèi)容,比如method,constantPool(常量池)等,這塊內(nèi)存是由多塊內(nèi)存組合起來的,所以可以認(rèn)為是不連續(xù)的內(nèi)存塊組成的。

這塊內(nèi)存是必須的,雖然叫做NoKlass Metaspace,但是也其實(shí)可以存klass的內(nèi)容,上面已經(jīng)提到了對(duì)應(yīng)場(chǎng)景。

Klass Metaspace和NoKlass Mestaspace都是所有classloader共享的,所以類加載器們要分配內(nèi)存,但是每個(gè)類加載器都有一個(gè)SpaceManager,來管理屬于這個(gè)類加載的內(nèi)存小塊。

如果Klass Metaspace用完了,那就會(huì)OOM了,不過一般情況下不會(huì),NoKlass Mestaspace是由一塊塊內(nèi)存慢慢組合起來的,在沒有達(dá)到限制條件的情況下,會(huì)不斷加長(zhǎng)這條鏈,讓它可以持續(xù)工作。

2.2 Metaspace的內(nèi)存分配與管理

Metaspace VM利用內(nèi)存管理技術(shù)來管理Metaspace。

這使得由不同的垃圾收集器來處理類元數(shù)據(jù)的工作,現(xiàn)在僅僅由Metaspace VM在Metaspace中通過C++來進(jìn)行管理。

Metaspace背后的一個(gè)思想是,類和它的元數(shù)據(jù)的生命周期是和它的類加載器的生命周期一致的。

也就是說,只要類的類加載器是存活的,在Metaspace中的類元數(shù)據(jù)也是存活的,不能被釋放。

每個(gè)類加載器存儲(chǔ)區(qū)叫做“a metaspace”。

這些metaspaces一起總體稱為”the Metaspace”。

僅僅當(dāng)類加載器不再存活,被垃圾收集器聲明死亡后,該類加載器對(duì)應(yīng)的metaspace空間才可以回收。

Metaspace空間沒有遷移和壓縮。

但是元數(shù)據(jù)會(huì)被掃描是否存在Java引用。

Metaspace VM使用一個(gè)塊分配器(chunking allocator)來管理Metaspace空間的內(nèi)存分配。

塊的大小依賴于類加載器的類型。

其中有一個(gè)全局的可使用的塊列表(a global free list of chunks)。

當(dāng)類加載器需要一個(gè)塊的時(shí)候,類加載器從全局塊列表中取出一個(gè)塊,添加到它自己維護(hù)的塊列表中。

當(dāng)類加載器死亡,它的塊將會(huì)被釋放,歸還給全局的塊列表。

塊(chunk)會(huì)進(jìn)一步被劃分成blocks,每個(gè)block存儲(chǔ)一個(gè)元數(shù)據(jù)單元(a unit of metadata)。

Chunk中Blocks的分配線性的(pointer bump)。

這些chunks被分配在內(nèi)存映射空間(memory mapped(mmapped) spaces)之外。

在一個(gè)全局的虛擬內(nèi)存映射空間(global virtual mmapped spaces)的鏈表,當(dāng)任何虛擬空間變?yōu)榭諘r(shí),就將該虛擬空間歸還回操作系統(tǒng)。


Metaspace的內(nèi)存分配與管理

2.3 Metaspace VM內(nèi)存碎片問題

先前提到的,Metaspace VM使用塊分配器(chunking allocator)。

chunk的大小取決于類加載器的類型。

由于類class并沒有一個(gè)固定的尺寸,這就存在這樣一種可能:

可分配的chunk的尺寸和需要的chunk的尺寸不相等,這就會(huì)導(dǎo)致內(nèi)存碎片。

內(nèi)存碎片的產(chǎn)生

Metaspace VM還沒有使用壓縮技術(shù),所以內(nèi)存碎片是現(xiàn)在的一個(gè)主要關(guān)注的問題。

2.4 Metaspace 總結(jié)

元空間的本質(zhì)和永久代類似,都是對(duì)JVM規(guī)范中方法區(qū)的實(shí)現(xiàn)。

不過元空間與永久代之間最大的區(qū)別在于:

元空間并不在虛擬機(jī)中,而是使用本地內(nèi)存。

因此,默認(rèn)情況下,元空間的大小僅受本地內(nèi)存限制,但可以通過參數(shù)來指定元空間的大小。


java8內(nèi)存模型圖

2.5 MetaSpace應(yīng)該掌握的知識(shí)

  1. 在 JDK 1.7 和 1.8 中將字符串常量池由永久代轉(zhuǎn)移到堆中
  2. 存放類相關(guān)信息的地方也不在heap(堆)中。在元空間里。
  3. 在jdk1.8中沒有永久代的概念
  4. metaspace其實(shí)由兩大部分組成
  • Klass Metaspace
    存放klass的,klass是我們熟知的class文件在jvm里的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu),這個(gè)空間的默認(rèn)大小是1G
  • NoKlass Metaspace
    專門來存klass相關(guān)的其他的內(nèi)容,比如method,constantPool(常量池)等,這塊內(nèi)存是由多塊內(nèi)存組合起來的,所以可以認(rèn)為是不連續(xù)的內(nèi)存塊組成的。這塊內(nèi)存是必須的

Klass Metaspace和NoKlass Mestaspace都是所有classloader共享的,所以類加載器們要分配內(nèi)存,但是每個(gè)類加載器都有一個(gè)SpaceManager,來管理屬于這個(gè)類加載的內(nèi)存小塊。

如果Klass Metaspace用完了,那就會(huì)OOM了,不過一般情況下不會(huì),NoKlass Mestaspace是由一塊塊內(nèi)存慢慢組合起來的,在沒有達(dá)到限制條件的情況下,會(huì)不斷加長(zhǎng)這條鏈,讓它可以持續(xù)工作。

5.metaspace主要相關(guān)參數(shù)

-XX:MetaspaceSize,初始空間大小,達(dá)到該值就會(huì)觸發(fā)垃圾收集進(jìn)行類型卸載,同時(shí)GC會(huì)對(duì)該值進(jìn)行調(diào)整:

  • 如果釋放了大量的空間,就適當(dāng)降低該值;
  • 如果釋放了很少的空間,那么在不超過MaxMetaspaceSize時(shí),適當(dāng)提高該值。

-XX:MaxMetaspaceSize,最大空間,默認(rèn)是沒有限制的。

-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空間容量的百分比,減少為分配空間所導(dǎo)致的垃圾收集

-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空間容量的百分比,減少為釋放空間所導(dǎo)致的垃圾收集

  1. Metaspace的內(nèi)存分配與管理 都應(yīng)該清楚
  2. 為什么要將永久代替換成Metaspace?
  • 字符串存在永久代中,容易出現(xiàn)性能問題和內(nèi)存溢出。
  • 類及方法的信息等比較難確定其大小,因此對(duì)于永久代的大小指定比較困難,太小容易出現(xiàn)永久代溢出,太大則容易導(dǎo)致老年代溢出。
  • 永久代會(huì)為 GC 帶來不必要的復(fù)雜度,并且回收效率偏低。
  • Oracle 可能會(huì)將HotSpot 與 JRockit 合二為一。

參見:
Java 8新特性探究(九)跟OOM:Permgen說再見吧:http://www.importnew.com/14933.html
JVM(二)Java8內(nèi)存劃分:https://blog.csdn.net/yjp198713/article/details/78759933
JVM源碼分析之Metaspace解密:http://www.cnblogs.com/paddix/p/5309550.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容