一、JVM內存之GC

一、JVM內存之GC
一、JVM內存之GC

1、JVM內存劃分爲堆內存和非堆內存

2、堆內存用途:存放對象,垃圾收集器就是收集這些對象,然後根據GC算法回收。

3、非堆內存用途:永久代,也稱爲方法區,存儲程序運行時長期存活的對象,比如類的元數據、方法、常量、屬性等。

元數據: calss的文本,路徑等
類屬性: static屬性
類方法;

  • 在JDK1.8版本廢棄了永久代,替代的是元空間(MetaSpace),元空間與永久代上類似,都是方法區的實現,他們最大區別是:元空間並不在JVM中,而是使用本地內存。

4、JDK1.8爲什麼要廢棄永久代?

移除永久代原因:爲融合HotSpot JVM與JRockit VM(新JVM技術)而做出的改變,因爲JRockit沒有永久代。
有了元空間就不再會出現永久代OOM問題;

5、移除永久代的影響?

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

元空間有注意有兩個參數:

MetaspaceSize :初始化元空間大小,控制發生GC閾值
MaxMetaspaceSize : 限制元空間大小上限,防止異常佔用過多物理內存

分代GC

對象將根據存活的時間被分爲:年輕代(Young Generation)、年老代(Old Generation)、永久代(Permanent Generation,也就是方法區)。

1、年輕代(Young Generation), 對應的GC機制被稱爲Minor GC或叫Young GC。注意,Minor GC並不代表年輕代內存不足,它事實上只表示在Eden區上的GC。

2、年老代(Old Generation),對象如果在年輕代存活了足夠長的時間而沒有被清理掉,則會被複制到年老代,年老代的空間一般比年輕代大,能存放更多的對象,在年老代上發生的GC次數也比年輕代少。當年老代內存不足時,將執行Major GC;

如果對象比較大(比如長字符串或大數組,集合),Young空間不足,則大對象會直接分配到老年代上(大對象可能觸發提前GC,應少用,更應避免使用短命的大對象)。用-XX:PretenureSizeThreshold來控制直接升入老年代的對象大小,大於這個值的對象會直接分配在老年代上。

 

3、Major GC就是Full GC嗎?

針對HotSpot VM的實現,它裏面的GC其實準確分類只有兩大種:
Partial GC:並不收集整個GC堆的模式

Young GC:只收集young gen的GC
Old GC:只收集old gen的GC。只有CMS的concurrent collection是這個模式
Full GC:收集整個堆,包括young gen、old gen、perm gen(如果存在的話)等所有部分的模式。

當有人說“major GC”的時候一定要問清楚他想要指的是上面的Full GC還是Old GC;

最簡單的分代式GC策略,按HotSpot VM的serial GC的實現來看,觸發條件是:

Young GC:當young gen中的eden區分配滿的時候觸發。注意young GC中有部分存活對象會晉升到old gen,所以young GC後old gen的佔用量通常會有所升高。
Full GC:1、當準備要觸發一次young GC時,如果發現統計數據說之前young GC的平均晉升大小比目前old gen剩餘的空間大,則不會觸發young GC而是轉爲觸發full GC(常見於CMS); 2、如果有perm gen的話,要在perm gen分配空間但已經沒有足夠空間時,也要觸發一次full GC;或者System.gc()、heap dump帶GC,默認也是觸發Full GC。

GC算法

1、標記-清除算法
一、JVM內存之GC
如圖所示,當進行過標記清除算法之後,出現了大量的非連續內存。當java堆需要分配一段連續的內存給一個新對象時,發現雖然內存清理出了很多的空閒,但是仍然需要繼續清理以滿足“連續空間”的要求。所以說,這種方法比較基礎,效率也比較低下。

2、標記-複製算法
爲了解決效率與內存碎片問題

一、JVM內存之GC
從圖中可以看出,整理後的內存十分規整,但是白白浪費一般的內存成本太高。然而這其實是很重要的一個收集算法,因爲現在的商業虛擬機都採用這種算法來回收新生代。根據這個算法衍生出現在主流的JVM 新生代內存模型:
一、JVM內存之GC

3、標記-整理算法
複製算法在極端情況下(存活對象較多)效率變得很低,並且需要有額外的空間進行分配擔保。所以在老年代中這種情況一般是不適合的。
一、JVM內存之GC
與標記-清除算法一樣,首先是標記對象,然而第二步是將存貨的對象向內存一段移動,整理出一塊較大的連續內存空間。

STW

stop - the -world
是在執行垃圾收集算法時,Java應用程序的其他所有線程都被掛起(除了垃圾收集幫助器之外)。Java中一種全局暫停現象,全局停頓,所有Java代碼停止,native代碼可以執行,但不能與JVM交互;這些現象多半是由於gc引起。

顯然上面的三類gc算法,老年代的標記 - 整理算法會觸發STW ;

垃圾收集器

串行收集器(Serial)

單線程。收集時,必須暫停應用的工作線程,直到收集結束。

並行收集器(Parallel)
適用於新生代 ;

多條垃圾收集線程並行工作,在多核CPU下效率更高,收集線程工作時應用線程仍然處於等待狀態

CMS收集器(Concurrent Mark Sweep)
適用於老年代; “Mostly Concurrent Mark and Sweep Garbage Collector”;
CMS收集器是縮短暫停應用時間爲目標而設計的,是基於標記-清除算法實現,整個過程分爲4個步驟,包括:

初始標記(Initial Mark)
併發標記(Concurrent Mark)
重新標記(Remark)
併發清除(Concurrent Sweep)

其中,初始標記、重新標記這兩個步驟仍然需要暫停應用線程。初始標記只是標記一下GC Roots能直接關聯到的對象,速度很快,併發標記階段是標記可回收對象,而重新標記階段則是爲了修正併發標記期間因用戶程序繼續運作導致標記產生變動的那一部分對象的標記記錄,這個階段暫停時間比初始標記階段稍長一點,但遠比並發標記時間段小。
由於整個過程中消耗最長的併發標記和併發清除過程收集器線程都可以與用戶線程一起工作,所以,CMS收集器內存回收與用戶一起併發執行的,大大減少了暫停時間。

GC收集分析

引用計數法和可達性分析算法

引用計數法:
給對象中添加一個引用計數器,每當有一個地方引用他時,計數器值就+1,;當引用失效時,計數器值就-1;任何時刻計數器爲0的對象就是不可能在被使用。
(1)、優點

判定效率很高

(2)、缺點

不會完全準確,因爲如果出現兩個對象相互引用的問題就不行了
一、JVM內存之GC
很明顯,到最後兩個實例都不再用了(都等於null了),但是GC卻無法回收,因爲引用數不是0,而是1,這就造成了內存泄漏。也很明顯,現在虛擬機都不採用此方式。
分析上述代碼
一、JVM內存之GC

GC ROOT

一個對象可以屬於多個root,GC root有幾下種:

Class - 由系統類加載器(system class loader)加載的對象,這些類是不能夠被回收的,他們可以以靜態字段的方式保存持有其它對象。我們需要注意的一點就是,通過用戶自定義的類加載器加載的類,除非相應的java.lang.Class實例以其它的某種(或多種)方式成爲roots,否則它們並不是roots,.
Thread - 活着的線程
Stack Local - Java方法的local變量或參數
JNI Local - JNI方法的local變量或參數
JNI Global - 全局JNI引用
Monitor Used - 用於同步的監控對象
Held by JVM - 用於JVM特殊目的由GC保留的對象,但實際上這個與JVM的實現是有關的。

GC 策略

垃圾回收器的可用組合:
一、JVM內存之GC
標紅組合爲現在主流的gc策略;

GC日誌分析:
1、Minor GC
一、JVM內存之GC

2016-08-23T02:23:07.219-0200 – GC發生的時間;
64.322 – GC開始,相對JVM啓動的相對時間,單位是秒;
GC – 區別MinorGC和FullGC的標識,這次代表的是MinorGC;
Allocation Failure – MinorGC的原因,在這個case裏邊,由於年輕代不滿足申請的空間,因此觸發了MinorGC;
ParNew – 收集器的名稱,它預示了年輕代使用一個並行的 mark-copy stop-the-world 垃圾收集器;
613404K->68068K – 收集前後年輕代的使用情況;
(613440K) – 整個年輕代的容量;
0.1020465 secs – 這個解釋用原滋原味的解釋:Duration for the collection w/o final cleanup.
10885349K->10880154K – 收集前後整個堆的使用情況;
(12514816K) – 整個堆的容量;
0.1021309 secs – ParNew收集器標記和複製年輕代活着的對象所花費的時間(包括和老年代通信的開銷、對象晉升到老年代時間、垃圾收集週期結束一些最後的清理對象等的花銷);
[Times: user=0.78 sys=0.01, real=0.11 secs] – GC事件在不同維度的耗時,具體的用英文解釋起來更加合理:
user – Total CPU time that was consumed by Garbage Collector threads during this collection
sys – Time spent in OS calls or waiting for system event
real – Clock time for which your application was stopped. With Parallel GC this number should be close to (user time + system time) divided by the number of threads used by the Garbage Collector. In this particular case 8 threads were used. Note that due to some activities not being parallelizable, it always exceeds the ratio by a certain amount.

2、晉升分析
開始的時候:整個堆的大小是 10885349K,年輕代大小是613404K,這說明老年代大小是 10885349-613404=10271945K,

收集完成之後:整個堆的大小是 10880154K,年輕代大小是68068K,這說明老年代大小是 10880154-68068=10812086K,

老年代的大小增加了:10812086-10271945=608209K,也就是說 年輕代到年老代promot了608209K的數據;

圖形分析:
一、JVM內存之GC

3、Full/Major GC
一、JVM內存之GC
Phase 1: Initial Mark
這是CMS中兩次stop-the-world事件中的第一次。它有兩個目標:一是標記老年代中所有的GC Roots;二是標記被年輕代中活着的對象引用的對象。
標記結果如下:
一、JVM內存之GC

一、JVM內存之GC

016-08-23T11:23:07.321-0200: 64.42 – GC事件開始,包括時鐘時間和相對JVM啓動時候的相對時間,下邊所有的階段改時間的含義相同;
CMS Initial Mark – 收集階段,開始收集所有的GC Roots和直接引用到的對象;
10812086K – 當前老年代使用情況;
(11901376K) – 老年代可用容量;
10887844K – 當前整個堆的使用情況;
(12514816K) – 整個堆的容量;
0.0001997 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] – 時間計量;

Phase 2: Concurrent Mark
這個階段會遍歷整個老年代並且標記所有存活的對象,從“初始化標記”階段找到的GC Roots開始。併發標記的特點是和應用程序線程同時運行。並不是老年代的所有存活對象都會被標記,因爲標記的同時應用程序會改變一些對象的引用等。
2016-08-23T11:23:07.321-0200: 64.425: [CMS-concurrent-mark-start]
2016-08-23T11:23:07.357-0200: 64.460: [CMS-concurrent-mark: 035/0.035 secs] [Times: user=0.07 sys=0.00, real=0.03 secs]
CMS-concurrent-mark – 併發收集階段,這個階段會遍歷整個年老代並且標記活着的對象;
035/0.035 secs – 展示該階段持續的時間和時鐘時間;
[Times: user=0.07 sys=0.00, real=0.03 secs] – 同上

Phase 3: Concurrent Preclean(預洗)
這個階段又是一個併發階段,和應用線程並行運行,不會中斷他們。前一個階段在並行運行的時候,一些對象的引用已經發生了變化,當這些引用發生變化的時候,JVM會標記堆的這個區域爲Dirty Card(包含被標記但是改變了的對象,被認爲"dirty"),這就是 Card Marking。
一、JVM內存之GC
在pre-clean階段,那些能夠從dirty card對象到達的對象也會被標記,這個標記做完之後,dirty card標記就會被清除了,如下:
一、JVM內存之GC
Phase 4: Concurrent Abortable(可中止) Preclean
又一個併發階段不會停止應用程序線程。這個階段嘗試着去承擔STW的Final Remark階段足夠多的工作。這個階段持續的時間依賴好多的因素,由於這個階段是重複的做相同的事情直到發生aboart的條件(比如:重複的次數、多少量的工作、持續的時間等等)之一纔會停止。

Phase 5: Final Remark
這個階段是CMS中第二個並且是最後一個STW的階段。該階段的任務是完成標記整個年老代的所有的存活對象。由於之前的預處理是併發的,它可能跟不上應用程序改變的速度,這個時候,STW是非常需要的來完成這個嚴酷考驗的階段。

通常CMS儘量運行Final Remark階段在年輕代是足夠乾淨的時候,目的是消除緊接着的連續的幾個STW階段。
一、JVM內存之GC

2016-08-23T11:23:08.447-0200: 65.550 – 同上;
CMS Final Remark – 收集階段,這個階段會標記老年代全部的存活對象,包括那些在併發標記階段更改的或者新創建的引用對象;
YG occupancy: 387920 K (613440 K) – 年輕代當前佔用情況和容量;
[Rescan (parallel) , 0.0085125 secs] – 這個階段在應用停止的階段完成存活對象的標記工作;
weak refs processing, 0.0000243 secs]65.559 – 第一個子階段,隨着這個階段的進行處理弱引用;
class unloading, 0.0013120 secs]65.560 – 第二個子階段(that is unloading the unused classes, with the duration and timestamp of the phase);
scrub string table, 0.0001759 secs – 最後一個子階段(that is cleaning up symbol and string tables which hold class-level metadata and internalized string respectively)
10812086K(11901376K) – 在這個階段之後老年代佔有的內存大小和老年代的容量;
11200006K(12514816K) – 在這個階段之後整個堆的內存大小和整個堆的容量;
0.0110730 secs – 這個階段的持續時間;
[Times: user=0.06 sys=0.00, real=0.01 secs] – 同上;

Phase 6: Concurrent Sweep
和應用線程同時進行,不需要STW。這個階段的目的就是移除那些不用的對象,回收他們佔用的空間並且爲將來使用。
一、JVM內存之GC
Phase 7: Concurrent Reset
這個階段併發執行,重新設置CMS算法內部的數據結構,準備下一個CMS生命週期的使用。

JVM內存參數配置

參數名稱 含義 默認值 解釋
-Xms 初始堆大小 物理內存的1/64 空餘堆內存小於40%時,JVM就會增大堆直到-Xmx的最大限制(MinHeapFreeRatio參數可以調整)
-Xmx 最大堆大小 物理內存的1/4 空餘堆內存大於70%時,JVM會減少堆直到 -Xms的最小限制(MaxHeapFreeRatio參數可以調整)
-Xmn 年輕代大小 eden+ 2*(survivor space),整個堆大小=年輕代大小 + 年老代大小 + 持久代大小
-XX:PermSize 設置持久代 物理內存的1/64 jdk8 無效
-XX:MaxPermSize 設置持久代 物理內存的1/4 jdk8 無效
-Xss 線程的棧大小 JDK5.0以後每個線程堆棧大小爲1M,以前每個線程堆棧大小爲256K,在相同物理內存下,減小這個值能生成更多的線程.但是操作系統對一個進程內的線程數還是有限制的,不能無限生成,經驗值在3000~5000左右
-XX:NewRatio 年輕代與年老代的比值 4 4表示年輕代與年老代所佔比值爲1:4,Xms=Xmx並且設置了Xmn的情況下,該參數不需要進行設置。
-XX:SurvivorRatio Eden區與Survivor區的大小比值 8 8,則一個Eden區與兩一個Survivor區與的比值爲8:1,一個Survivor區佔整個年輕代的1/10
-XX:MaxTenuringThreshold 對象最大年齡 如果設置爲0的話,則年輕代對象不經過Survivor區,直接進入年老代. 對於年老代比較多的應用,可以提高效率.如果將此值設置爲一個較大值,則年輕代對象會在Survivor區進行多次複製,這樣可以增加對象再年輕代的存活 時間,增加在年輕代即被回收的概率
該參數只有在串行GC時纔有效.

CMS 配置

參數名稱 含義 默認值 解釋
-XX:+UseConcMarkSweepGC 使用CMS內存收集 測試中配置這個以後,-XX:NewRatio=4的配置失效了,原因不明.所以,此時年輕代大小最好用-Xmn設置.

並行收集器配置

參數名稱 含義 默認值 解釋
-XX:+UseParallelGC 選擇並行收集器.此配置僅對年輕代有效.
-XX:+UseParNewGC 設置年輕代爲並行收集 可與CMS收集同時使用
JDK5.0以上,JVM會根據系統配置自行設置,所以無需再設置此值

GC 日誌配置

參數名稱 含義 默認值 解釋
-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]

CATALINA_OPTS="-server"
CATALINA_OPTS="${CATALINA_OPTS} -Xms5184m -Xmx5184m"
CATALINA_OPTS="${CATALINA_OPTS} -XX:PermSize=512m -XX:MaxPermSize=512m"
CATALINA_OPTS="${CATALINA_OPTS} -Xmn2g"
CATALINA_OPTS="${CATALINA_OPTS} -XX:MaxDirectMemorySize=1g"
CATALINA_OPTS="${CATALINA_OPTS} -XX:SurvivorRatio=10"
CATALINA_OPTS="${CATALINA_OPTS} -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSMaxAbortablePrecleanTime=5000"
CATALINA_OPTS="${CATALINA_OPTS} -XX:+CMSClassUnloadingEnabled -XX:CMSInitiatingOccupancyFraction=80 -XX:+UseCMSInitiatingOccupancyOnly"
CATALINA_OPTS="${CATALINA_OPTS} -XX:+ExplicitGCInvokesConcurrent -Dsun.rmi.dgc.server.gcInterval=2592000000 -Dsun.rmi.dgc.client.gcInterval=2592000000"
CATALINA_OPTS="${CATALINA_OPTS} -XX:ParallelGCThreads=${CPU_COUNT}"
CATALINA_OPTS="${CATALINA_OPTS} -Xloggc:${MI_LOGS}/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps"
CATALINA_OPTS="${CATALINA_OPTS} -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${MI_LOGS}/java.hprof"
CATALINA_OPTS="${CATALINA_OPTS} -Djava.awt.headless=true"
CATALINA_OPTS="${CATALINA_OPTS} -Dsun.net.client.defaultConnectTimeout=10000"
CATALINA_OPTS="${CATALINA_OPTS} -Dsun.net.client.defaultReadTimeout=30000"
CATALINA_OPTS="${CATALINA_OPTS} -DJM.LOG.PATH=${MI_LOGS}"
CATALINA_OPTS="${CATALINA_OPTS} -DJM.SNAPSHOT.PATH=${MIDDLEWARE_SNAPSHOTS}"
CATALINA_OPTS="${CATALINA_OPTS} -Dfile.encoding=${JAVA_FILE_ENCODING}"
CATALINA_OPTS="${CATALINA_OPTS} -Dfastjson.parser.autoTypeSupport=true"

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