JVM內存區域詳解(Eden Space、Survivor Space、Old Gen、Code Cache和Perm Gen)

參考文章:

https://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html

https://www.cnblogs.com/duanxz/p/3520829.html

 

一、JVM內存分類

JVM區域總體分兩類,heap區和非heap區。

heap區又分爲:

 

  • Eden Space(伊甸園)、
  • Survivor Space(倖存者區)、
  • Old Gen(老年代)。

 

非heap區根據JDK版本的不同,分類有所不同,在Java 8之前分爲:

  • Code Cache(代碼緩存區);
  • Perm Gen(永久代);
  • Jvm Stack(java虛擬機棧);
  • Local Method Statck(本地方法棧);

 

java8之前的jvm結構

在Java8之後,將Perm Gen徹底的移除,取而代之的是Metaspace(元空間):

  • Code Cache(代碼緩存區);
  • Metaspace(元空間);
  • Compressed Class Space(類指針壓縮空間)
  • Jvm Stack(java虛擬機棧);
  • Local Method Statck(本地方法棧);

 

二、Java對象的生命週期

我是一個普通的Java對象,我出生在Eden區,在Eden區我還看到和我長的很像的小兄弟(其他java對象),我們在Eden區中玩了挺長時間。有一天Eden區中的人實在是太多了(會觸發Young GC,每次GC加一歲)),我就被迫去了Survivor區的“To”區,自從去了Survivor區,我就開始漂了,有時候在Survivor的“From”區,有時候在Survivor的“To”區,居無定所(每次Young GC都需要Survivor區中的from區和to區"對調")。直到我18歲的時候(進行了18次Young GC),爸爸說我成人了,該去社會上闖闖了。於是我就去了年老代那邊,年老代里人很多,並且年齡都挺大的,我在這裏也認識了很多人。在年老代裏,我生活了20年,然後被回收(Old GC)。

 

三、JVM區域介紹

  • Eden Space

字面意思是伊甸園,對象被創建的時候首先放到這個區域,進行垃圾回收後,不能被回收的對象被放入到空的survivor區域。

  • Survivor Space倖存者區

用於保存在eden space內存區域中經過垃圾回收後沒有被回收的對象。Survivor有兩個,分別爲To Survivor、 From Survivor,這個兩個區域的空間大小是一樣的。執行垃圾回收的時候Eden區域不能被回收的對象被放入到空的survivor(也就是To Survivor,同時Eden區域的內存會在垃圾回收的過程中全部釋放),另一個survivor(即From Survivor)裏不能被回收的對象也會被放入這個survivor(即To Survivor),然後To Survivor 和 From Survivor的標記會互換,始終保證一個survivor是空的。

 

 

Eden Space和Survivor Space都屬於新生代,新生代中執行的垃圾回收被稱之爲Minor GC(因爲是對新生代進行垃圾回收,所以又被稱爲Young GC),每一次Young GC後留下來的對象age加1。

注:GC爲Garbage Collection,垃圾回收。

可以通過設置JVM參數-XX:NewSize和-XX:MaxNewSize來分別調節新生代的初始化內存和最大內存,通過-XX:SurvivorRatio來設置伊甸園和倖存區的比例,比如設置:

-XX:NewSize=3072m -XX:MaxNewSize=3072m -XX:SurvivorRatio=8

整個新生代的內存將會達到3072M,然後伊甸園佔據整個新生代8/10的內存,from和to倖存區各佔據1/10。

  • Old Gen老年代

用於存放新生代中經過多次垃圾回收仍然存活的對象,也有可能是新生代分配不了內存的大對象會直接進入老年代。經過多次垃圾回收都沒有被回收的對象,這些對象的年代已經足夠old了,就會放入到老年代。當老年代被放滿的之後,虛擬機會進行垃圾回收,稱之爲Major GC。由於Major GC除併發GC外均需對整個堆進行掃描和回收,因此又稱爲Full GC。

 

heap區,即堆內存,整個堆大小=年輕代大小 + 老年代大小。堆內存默認爲物理內存的1/64(<1GB);默認空餘堆內存小於40%時,JVM就會增大堆直到-Xmx的最大限制,可以通過MinHeapFreeRatio參數進行調整;默認空餘堆內存大於70%時,JVM會減少堆直到-Xms的最小限制,可以通過MaxHeapFreeRatio參數進行調整。

 

下面我們來認識下非堆內存(非heap區)

  • Code Cache代碼緩存區

它主要用於存放JIT所編譯的代碼。CodeCache代碼緩衝區的大小在client模式下默認最大是32m,在server模式下默認是48m,這個值也是可以設置的,它所對應的JVM參數爲ReservedCodeCacheSize 和 InitialCodeCacheSize,可以通過如下的方式來爲Java程序設置:

-XX:ReservedCodeCacheSize=128m

CodeCache緩存區是可能被充滿的,當CodeCache滿時,後臺會收到CodeCache is full的警告信息,如下所示:

“CompilerThread0” java.lang.OutOfMemoryError: requested 2854248 bytes for Chunk::new. Out of swap space?

注:JIT編譯器是在程序運行期間,將Java字節碼編譯成平臺相關的二進制代碼。正因爲此編譯行爲發生在程序運行期間,所以該編譯器被稱爲Just-In-Time編譯器。

 

  • Perm Gen

全稱是Permanent Generation space,是指內存的永久保存區域,因而稱之爲永久代。這個內存區域用於存放Class和Meta的信息,Class在被 Load的時候被放入這個區域。因爲Perm裏存儲的東西永遠不會被JVM垃圾回收的,所以如果你的應用程序LOAD很多CLASS的話,就很可能出現PermGen space錯誤。默認大小爲物理內存的1/64。該區域在java8之後被meta

 

  • Metaspace(元空間)

Metaspace(元空間)是本地內存的一塊內存空間,在Java8之後用來取代Perm Gen(永久區,存在堆內存中)。Metaspace(元空間)分爲以下兩部分:

  1. Klass Metaspace:Klass Metaspace。

就是用來存klass的,klass是我們熟知的class文件在jvm裏的運行時數據結構,不過有點要提的是我們看到的類似A.class其實是存在heap裏的,是java.lang.Class的一個對象實例。這塊內存是緊接着Heap的,和我們之前的perm一樣,這塊內存大小可通過-XX:CompressedClassSpaceSize參數來控制,這個參數默認是1G,但是這塊內存也可以沒有,假如沒有開啓壓縮指針就不會有這塊內存,這種情況下klass都會存在NoKlass Metaspace裏,另外如果我們把-Xmx設置大於32G的話,其實也是沒有這塊內存的,因爲會這麼大內存會關閉壓縮指針開關。還有就是這塊內存最多隻會存在一塊。

  1. NoKlass Metaspace:NoKlass Metaspace。

專門來存klass相關的其他的內容,比如method,constantPool等,這塊內存是由多塊內存組合起來的,所以可以認爲是不連續的內存塊組成的。這塊內存是必須的,雖然叫做NoKlass Metaspace,但是也其實可以存klass的內容,上面已經提到了對應場景。

Klass Metaspace和NoKlass Mestaspace都是所有classloader共享的,所以類加載器們要分配內存,但是每個類加載器都有一個SpaceManager,來管理屬於這個類加載的內存小塊。如果Klass Metaspace用完了,那就會OOM了,不過一般情況下不會,NoKlass Mestaspace是由一塊塊內存慢慢組合起來的,在沒有達到限制條件的情況下,會不斷加長這條鏈,讓它可以持續工作。

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

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

  -XX:MaxMetaspaceSize,最大空間,默認是沒有限制的。 

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

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

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

  -verbose參數是爲了獲取類型加載和卸載的信息

 

  • 類指針壓縮空間(Compressed Class Pointer Space)

只有是64位平臺上啓用了類指針壓縮纔會存在這個區域。對於64位平臺,爲了壓縮JVM對象中的_klass指針的大小,引入了類指針壓縮空間(Compressed Class Pointer Space)。要想理解什麼是類指針壓縮空間(Compressed Class Pointer Space,我們需要先了解_mark和_klass指針。

  1. 理解_mark和_klass指針

要想理解下面這張圖,你得搞清楚這些指針都是什麼東西。

JVM中,每個對象都有一個指向它自身類的指針,不過這個指針只是指向具體的實現類,而不是接口或者抽象類。

對於32位的JVM:

_mark : 4字節常量

_klass: 指向類的4字節指針 對象的內存佈局中的第二個字段( _klass,在32位JVM中,相對對象在內存中的位置的偏移量是4,64位的是8)指向的是內存中對象的類定義。

未開啓指針壓縮的64位JVM:

_mark : 8字節常量

_klass: 指向類的8字節的指針

開啓了指針壓縮的64位JVM:

_mark : 8字節常量

_klass: 指向類的4字節的指針

 

 

指針壓縮概要

  • 64位平臺上默認打開
  • 使用-XX:+UseCompressedOops壓縮對象指針 "oops"指的是普通對象指針("ordinary" object pointers)。 Java堆中對象指針會被壓縮成32位。 使用堆基地址(如果堆在低26G內存中的話,基地址爲0)
  • 使用-XX:+UseCompressedClassPointers選項來壓縮類指針
  • 對象中指向類元數據的指針會被壓縮成32位
  • 類指針壓縮空間會有一個基地址

元空間和類指針壓縮空間的區別

  • 類指針壓縮空間只包含類的元數據,比如InstanceKlass, ArrayKlass 僅當打開了UseCompressedClassPointers選項才生效 爲了提高性能,Java中的虛方法表也存放到這裏 這裏到底存放哪些元數據的類型,目前仍在減少
  • 元空間包含類的其它比較大的元數據,比如方法,字節碼,常量池等。

 

  • JVM棧(Jvm Stack)

每當線程調用一個Java方法時,虛擬機都會在該線程的Java棧中壓入一個新幀。而這個新幀自然就成爲了當前幀。在執行這個方法時,它使用這個幀來存儲參數、局部變量、中間運算結果等數據。

比如,當我們執行 Object obj=new Object("123"); 實際上,中間會分成三個步驟(由於編譯器優化,2和3步可能會對調):

第一步,在jvm棧中分配一片內存,就是變量obj。

第二步,在堆中分配一片內存,用來存放Object對象。

第三步,將創建的變量指向堆中的內存。

  Java方法可以以兩種方式完成。一種通過return返回的,稱爲正常返回;一種是通過拋出異常而異常終止的。不管以哪種方式返回,虛擬機都會將當前幀彈出Java棧然後釋放掉,這樣上一個方法的幀就成爲當前幀了。

  Java幀上的所有數據都是此線程私有的。任何線程都不能訪問另一個線程的棧數據,因此我們不需要考慮多線程情況下棧數據的訪問同步問題。當一個線程調用一個方法時,方法的的局部變量保存在調用線程Java棧的幀中。只有一個線程能總是訪問那些局部變量,即調用方法的線程。

  • 本地方法棧

java調用操作系統自身方法用來存參數、局部變量和中間運算結果的棧。

附:

JVM參數的含義 

參數名稱

含義

默認值

 

-Xms

初始堆大小

物理內存的1/64(<1GB)

默認(MinHeapFreeRatio參數可以調整)空餘堆內存小於40%時,JVM就會增大堆直到-Xmx的最大限制.

-Xmx

最大堆大小

物理內存的1/4(<1GB)

默認(MaxHeapFreeRatio參數可以調整)空餘堆內存大於70%時,JVM會減少堆直到 -Xms的最小限制

-Xmn

年輕代大小(1.4or lator)

 

注意:此處的大小是(eden+ 2 survivor space).與jmap -heap中顯示的New gen是不同的。

整個堆大小=年輕代大小 + 年老代大小 + 持久代大小.

增大年輕代後,將會減小年老代大小.此值對系統性能影響較大,Sun官方推薦配置爲整個堆的3/8

-XX:NewSize

設置年輕代大小(for 1.3/1.4)

 

 

-XX:MaxNewSize

年輕代最大值(for 1.3/1.4)

 

 

-XX:PermSize

設置持久代(perm gen)初始值

物理內存的1/64

 

-XX:MaxPermSize

設置持久代最大值

物理內存的1/4

 

-Xss

每個線程的堆棧大小

 

JDK5.0以後每個線程堆棧大小爲1M,以前每個線程堆棧大小爲256K.更具應用的線程所需內存大小進行 調整.在相同物理內存下,減小這個值能生成更多的線程.但是操作系統對一個進程內的線程數還是有限制的,不能無限生成,經驗值在3000~5000左右

一般小的應用, 如果棧不是很深, 應該是128k夠用的 大的應用建議使用256k。這個選項對性能影響比較大,需要嚴格的測試。(校長)

和threadstacksize選項解釋很類似,官方文檔似乎沒有解釋,在論壇中有這樣一句話:"”

-Xss is translated in a VM flag named ThreadStackSize”

一般設置這個值就可以了。

-XX:ThreadStackSize

Thread Stack Size

 

(0 means use default stack size) [Sparc: 512; Solaris x86: 320 (was 256 prior in 5.0 and earlier); Sparc 64 bit: 1024; Linux amd64: 1024 (was 0 in 5.0 and earlier); all others 0.]

-XX:NewRatio

年輕代(包括Eden和兩個Survivor區)與年老代的比值(除去持久代)

 

-XX:NewRatio=4表示年輕代與年老代所佔比值爲1:4,年輕代佔整個堆棧的1/5

Xms=Xmx並且設置了Xmn的情況下,該參數不需要進行設置。

-XX:SurvivorRatio

Eden區與Survivor區的大小比值

 

設置爲8,則兩個Survivor區與一個Eden區的比值爲2:8,一個Survivor區佔整個年輕代的1/10

-XX:LargePageSizeInBytes

內存頁的大小不可設置過大, 會影響Perm的大小

 

=128m

-XX:+UseFastAccessorMethods

原始類型的快速優化

 

 

-XX:+DisableExplicitGC

關閉System.gc()

 

這個參數需要嚴格的測試

-XX:MaxTenuringThreshold

垃圾最大年齡

 

如果設置爲0的話,則年輕代對象不經過Survivor區,直接進入年老代. 對於年老代比較多的應用,可以提高效率.如果將此值設置爲一個較大值,則年輕代對象會在Survivor區進行多次複製,這樣可以增加對象再年輕代的存活 時間,增加在年輕代即被回收的概率

該參數只有在串行GC時纔有效.

-XX:+AggressiveOpts

加快編譯

 

 

-XX:+UseBiasedLocking

鎖機制的性能改善

 

 

-Xnoclassgc

禁用垃圾回收

 

 

-XX:SoftRefLRUPolicyMSPerMB

每兆堆空閒空間中SoftReference的存活時間

1s

softly reachable objects will remain alive for some amount of time after the last time they were referenced. The default value is one second of lifetime per free megabyte in the heap

-XX:PretenureSizeThreshold

對象超過多大是直接在舊生代分配

0

單位字節 新生代採用Parallel Scavenge GC時無效

另一種直接在舊生代分配的情況是大的數組對象,且數組中無外部引用對象.

-XX:TLABWasteTargetPercent

TLAB佔eden區的百分比

1%

 

-XX:+CollectGen0First

FullGC時是否先YGC

false

 

並行收集器相關參數

-XX:+UseParallelGC

Full GC採用parallel MSC

(此項待驗證)

 

選擇垃圾收集器爲並行收集器.此配置僅對年輕代有效.即上述配置下,年輕代使用併發收集,而年老代仍舊使用串行收集.(此項待驗證)

-XX:+UseParNewGC

設置年輕代爲並行收集

 

可與CMS收集同時使用

JDK5.0以上,JVM會根據系統配置自行設置,所以無需再設置此值

-XX:ParallelGCThreads

並行收集器的線程數

 

此值最好配置與處理器數目相等 同樣適用於CMS

-XX:+UseParallelOldGC

年老代垃圾收集方式爲並行收集(Parallel Compacting)

 

這個是JAVA 6出現的參數選項

-XX:MaxGCPauseMillis

每次年輕代垃圾回收的最長時間(最大暫停時間)

 

如果無法滿足此時間,JVM會自動調整年輕代大小,以滿足此值.

-XX:+UseAdaptiveSizePolicy

自動選擇年輕代區大小和相應的Survivor區比例

 

設置此選項後,並行收集器會自動選擇年輕代區大小和相應的Survivor區比例,以達到目標系統規定的最低相應時間或者收集頻率等,此值建議使用並行收集器時,一直打開.

-XX:GCTimeRatio

設置垃圾回收時間佔程序運行時間的百分比

 

公式爲1/(1+n)

-XX:+ScavengeBeforeFullGC

Full GC前調用YGC

true

Do young generation GC prior to a full GC. (Introduced in 1.4.1.)

CMS相關參數

-XX:+UseConcMarkSweepGC

使用CMS內存收集

 

測試中配置這個以後,-XX:NewRatio=4的配置失效了,原因不明.所以,此時年輕代大小最好用-Xmn設置.???

-XX:+AggressiveHeap

 

 

試圖是使用大量的物理內存

長時間大內存使用的優化,能檢查計算資源(內存, 處理器數量)

至少需要256MB內存

大量的CPU/內存, (在1.4.1在4CPU的機器上已經顯示有提升)

-XX:CMSFullGCsBeforeCompaction

多少次後進行內存壓縮

 

由於併發收集器不對內存空間進行壓縮,整理,所以運行一段時間以後會產生"碎片",使得運行效率降低.此值設置運行多少次GC以後對內存空間進行壓縮,整理.

-XX:+CMSParallelRemarkEnabled

降低標記停頓

 

 

-XX+UseCMSCompactAtFullCollection

在FULL GC的時候, 對年老代的壓縮

 

CMS是不會移動內存的, 因此, 這個非常容易產生碎片, 導致內存不夠用, 因此, 內存的壓縮這個時候就會被啓用。 增加這個參數是個好習慣。

可能會影響性能,但是可以消除碎片

-XX:+UseCMSInitiatingOccupancyOnly

使用手動定義初始化定義開始CMS收集

 

禁止hostspot自行觸發CMS GC

-XX:CMSInitiatingOccupancyFraction=70

使用cms作爲垃圾回收

使用70%後開始CMS收集

92

爲了保證不出現promotion failed(見下面介紹)錯誤,該值的設置需要滿足以下公式CMSInitiatingOccupancyFraction計算公式

-XX:CMSInitiatingPermOccupancyFraction

設置Perm Gen使用到達多少比率時觸發

92

 

-XX:+CMSIncrementalMode

設置爲增量模式

 

用於單CPU情況

-XX:+CMSClassUnloadingEnabled

 

 

 

輔助信息

-XX:+PrintGC

 

 

輸出形式:

[GC 118250K->113543K(130112K), 0.0094143 secs]

[Full GC 121376K->10414K(130112K), 0.0650971 secs]

-XX:+PrintGCDetails

 

 

輸出形式:[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs]

[GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs]

-XX:+PrintGCTimeStamps

 

 

 

-XX:+PrintGC:PrintGCTimeStamps

 

 

可與-XX:+PrintGC -XX:+PrintGCDetails混合使用

輸出形式:11.851: [GC 98328K->93620K(130112K), 0.0082960 secs]

-XX:+PrintGCApplicationStoppedTime

打印垃圾回收期間程序暫停的時間.可與上面混合使用

 

輸出形式:Total time for which application threads were stopped: 0.0468229 seconds

-XX:+PrintGCApplicationConcurrentTime

打印每次垃圾回收前,程序未中斷的執行時間.可與上面混合使用

 

輸出形式:Application time: 0.5291524 seconds

-XX:+PrintHeapAtGC

打印GC前後的詳細堆棧信息

 

 

-Xloggc:filename

把相關日誌信息記錄到文件以便分析.

與上面幾個配合使用

 

 

-XX:+PrintClassHistogram

garbage collects before printing the histogram.

 

 

-XX:+PrintTLAB

查看TLAB空間的使用情況

 

 

XX:+PrintTenuringDistribution

查看每次minor GC後新的存活週期的閾值

 

Desired survivor size 1048576 bytes, new threshold 7 (max 15)

new threshold 7即標識新的存活週期的閾值爲7。

 

 

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