java_opts gc回收器_JVM之垃圾回收机制(GC)
JVM之垃圾回收機制全解(GC)文章底部有思維導圖,較為清晰,可參考
導讀:垃圾回收是Java體系中最重要的組成部分之一,其提供了一套全自動的內存管理方案,要想掌握這套管理方案,就必須了解垃圾回收器的工作原理。本文介紹了垃圾回收的概念,算法,垃圾回收器及我在工作中遇到的一些關于GC的優化實例。
先來簡單了解下JVM:
-------------------------------------------------------------
一、heap內存劃分
-------------------------------------------------------------
1.年輕代:分三個區。一個Eden區,兩個Survivor區(from Survivor(s0)區和to Survivor(s1)區)。
大部分對象在Eden區中生成。當Eden區滿時,還存活的對象將被復制到Survivor區(兩個中的一個),當這個Survivor區滿時,此區的存活對象將被復制到另外一個Survivor區,當這個Survivor去也滿了的時候,從第一個Survivor區復制過來的并且此時還存活的對象,將被復制“年老區(Tenured)”。
2.年老代
在年輕代中經歷了N次((ParNew默認15))垃圾回收后仍然存活的對象,就會被放到年老代中。年輕代放不下的大對象直接進入老年代。
tip1:對象動態年齡計算規則
虛擬機并不是永遠地要求對象的年齡必須達到了MaxTenuringThreshold(默認15次)才能晉升老年代,如果在Survivor空間中相同年齡所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象就可以直接進入老年代,無須等到MaxTenuringThreshold中要求的年齡。
3.持久代
用于存放靜態文件,如今Java類、方法等
JDK1.8中,永久代已經從java堆中移除,String直接存放在堆中,類的元數據存儲在meta space中,meta space占用外部內存,不占用堆內存。
-------------------------------------------------------------
二、GC回收算法
-------------------------------------------------------------
2.1 標記清除算法:標記清除分為兩個階段,標記階段(標記從根節點開始的所有可達對象,未標記即未被引用)和清除階段。缺點:兩個階段效率都很低;回收后內存空間不連續,產生碎片多,易導致提前GC。
2.2 復制算法:內存等分兩塊,相互復制存活的對象后清洗垃圾 缺點:內存利用率低
2.3 標記壓縮法:先標記,然后存活的向一段移動,清理存活端標記以外的內存。(老年代使用,無需需要第二塊相同的內存) 優缺點:無內存碎片,但是耗時。
2.4 分代算法:復制算法(新生代使用) ,標記壓縮法和標記清除法(老年代使用)。卡表(數據結構,一個比特位的集合),用來表示老年代對象是否持有新生代對象的引用,新生代無需再花時間確認對象是否被持有,可以加快新生代回收的速度。
2.5 分區算法:將整個堆空間劃分為連續不同的小的空間,獨立管理,獨立回收。
垃圾回收基本思想在于如何判斷對象的可觸及性。根據標記清除算法,可以掃描出root節點未觸及持有的對象,但一個無法觸及持有的對象有可能在某個時間下使自己復活。
對象的可觸及性的三種狀態:
可觸及的
可復活的(finalize()函數)
不可觸及的(finalize()函數只能調用一次)
2.6 引用和可觸及的強度分為4個級別
強引用:任何時候都不會被系統回收,亦可能會引起OOM。 例:StringBuffer str = new StringBuffer("juejin");
軟引用:GC不一定回收,但堆空間不足時會被回收。OOM之前一定會回收,所以軟引用不會引起OOM。 使用SoftReference創建的對象。
弱引用:發現即回收。使用WeakReference創建的對象。使用PhantomReference創建的對象。
虛引用:隨時可回收。
-------------------------------------------------------------
三、分代垃圾回收
-------------------------------------------------------------
3.1 young代采用復制算法
3.2 old代使用標記清除或者標記清理
3.3 Tip:對象優先在Eden去分配,大的對象直接進入老年代,長期存活對象進入老年代。
-------------------------------------------------------------
四、垃圾回收器
-------------------------------------------------------------
tip:Stop The World(STW)1、為了讓垃圾回收器可以正常切高效執行。2、保證了系統某個瞬間的一致性。3、有益于垃圾回收器更好地標記垃圾對象。
4.1 串行回收器
單線程GC,啟動時會停止應用,適用于配置小的服務器(1C2G),基本已棄用
4.2 并行回收器PS(吞吐量優先)
JDK1.6~1.8默認使用。垃圾線程并行,啟動時應用會等待。
PS的新生代回收器有兩個。
(1)、ParNew回收器:多線程執行垃圾回收。PS的線程數量可以用-XX:ParallelGCThreads指定。當CPU<8時,ParallelGCThreads的值=CPU,CPU>8時,ParallelGCThreads的值=3+((5*CPU_count)/8)。適用于交互較弱的場景。JDK1.8以上已經被刪除。
(2)、Parallel回收器:與ParNew一樣是多線程獨占式。但其特點是關注系統的吞吐量(吞吐量:花費在垃圾收集時間和花費在應用時間的占比)。
使用方法:-XX:+UseParallelGC(設置老年代-XX:+UseParallelOidGC)
4.3 并發回收器(響應時間優先)(并行GC前會額外觸發新生代的GC)
與并行回收器不相同的是,并發收集器是非獨占式,在進行垃圾回收的時候應用程序也可以運行。
主要有Concurrent Mask Sweep(CMS)和G1
JDK1.9默認使用G1。適用于對響應時間有要求的場景。響應時間:花費在應用時間和花費在垃圾收集時間的占比。
CMS(以獲取最短回收停頓時間為目標的收集器,基于并發“標記清理”實現)
過程:1)初始標記(標記root對象) 2)并發標記 3)預清理(準備及控制停頓時間) 4)重新標記 5)并發清除 6)并發重置
優點:并發收集、低停頓。
缺點:
1)CMS對CPU資源敏感。在并發階段,它雖然不會導致用戶線程停頓,但是會因為占用了一部分線程而導致應用程序變慢,總吞吐量會降低。
2)CMS無法處理浮動垃圾,可能會出現“Concurrent Mode Failure(并發模式故障)”失敗而導致Full GC產生。
3)CMS容易出現大量空間碎片。當空間碎片過多,將會給大對象分配帶來很大的麻煩,往往會出現老年代還有很大空間剩余,但是無法找到足夠大的連續空間來分配當前對象,不得不提前觸發一次Full GC。
4)老年代垃圾回收過程中,如果出現資源不夠用,則會強制進行老年代串行回收,應用暫停時間更長,影響更大。
G1(面向服務端應用的垃圾收集器)
1.7正式使用,且使用了全新的算法,看起來有取代CMS的趨勢。
保留了分代的概念,但是從堆結構上看,分代內存并不是連續的。如圖:
在并行性和并發性的基礎上,可以同時兼顧年輕代和年老代,還可以進行空間整理,每次GC之后會自動進行碎片整理,減少碎片空間。最后還有可預見性,G1可以選取部分區域進行內存回收。
過程:1)初始標記(標記root對象)(eden區會被清空) 2)根區域掃描 3)并發標記 4)重新標記 5)獨占清理 (計算各個區域存活對象和GC回收比例)6)并發清理
混合回收:在年輕代滿時,觸發年輕代收集;隨著老年代內存增長,當到達IHOP閾值-XX:InitiatingHeapOccupancyPercent(老年代占整堆比,默認45%)時,G1開始準備收集老年代空間。首先經歷并發標記周期,識別出垃圾占比較高的老年代分區。但隨后G1并不會馬上開始一次混合收集,而是讓應用線程先運行一段時間,等待觸發一次年輕代收集。在這次STW中,G1將保準整理混合收集周期。接著再次讓應用線程運行,當接下來的幾次年輕代收集時,將會有老年代分區加入到CSet中,即觸發混合收集,這些連續多次的混合收集稱為混合收集周期(Mixed Collection Cycle)
特點:
1、并行于并發:G1能充分利用CPU多核,使用多個CPU來縮短stop-The-World停頓時間。
2、分代收集:雖然G1可以不需要其他收集器配合就能獨立管理整個GC堆,但是還是保留了分代的概念。
3、空間整合:與CMS的“標記--清理”算法不同,G1從整體來看是基于“標記整理”算法實現的收集器;從局部上來看是基于“復制”算法實現的。
4、可預測的停頓:這是G1相對于CMS的另一個大優勢,降低停頓時間是G1和CMS共同的關注點,但G1除了追求低停頓外,還能建立可預測的停頓時間模型,能讓使用者明確指定在一個長度為M毫秒的時間片段內。
-------------------------------------------------------------
五、調優思路
-------------------------------------------------------------
5.1 前瞻
1、嘗試多種垃圾回收器,G1并不是最好的
2、并發不等于并行。垃圾回收的過程實際上有兩步,啟動GC周期和GC自身運行,這是不同的兩件事。并發針對的是GC周期,而并行針對GC算法自身。
3、平均事務時間不是最需要被關注的指標,有可能用戶正好經歷了那個長時間GC的場景,那將是毀滅性的。
4、GC調優并不能解決所有的事。如果程序修改程度大,那應該優先優化架構及代碼。
5、GC日志并不會對性能造成太大的影響,在GC未被優化之前,開啟GC日志是有必要的。
6、降低新對象的分配率可以改善GC的運行狀況。粗略地把系統中的對象分為三種:長命(long-lived)對象,對它們我們一般做不了什么;中等壽命(mid-lived)對象,最大的問題可能出現在這;短命(short-lived)對象,它們的釋放和回收通常都很快,在下個GC周期來臨時就會消失
5.2 思路
1、理解應用需求和問題。
2、掌握GC的狀態。
3、思考選擇的GC是否符合我們的應用特征。
4、分析確認需要調整的參數。
5、驗證調優。
5.3 查看設置參數
java -XX:+PrintFlagsInitial 查看初始值
-XX:+PrintFlagsFinal 查看最終值(初始值可能被修改掉)
-Xms 默認情況下堆內存的64分之一
-Xmx 默認情況下對內存的4分之一
-Xmn 默認情況下堆內存的3分之一
-XX:NewRatio 默認為2
-XX:SurvivorRatio 默認為8
-XX:+PrintGCDetails 開啟GC詳細日志-Xloggc:/cpic/cpicapp/perfma/xowl/../logs/xowl/gc.log -XX:+PrintGCDetails
5.4 GC一般合理表現
分析結果顯示GC耗時在0.1-0.3秒以內的話,一般不需要花費額外的時間做GC調優。然而, 如果GC耗時達到1-3秒甚至10秒以上,就需要立即對系統進行GC調優 。
Minor GC執行迅速(50毫秒以內)
Minor GC執行不頻繁(間隔10秒左右一次)
Full GC執行迅速(1秒以內)
Full GC執行不頻繁(間隔10分鐘左右一次)
-------------------------------------------------------------
六、參數調優
-------------------------------------------------------------
6.1 PS
6.1.1 吞吐量:-XX:GCTimeRatio=垃圾收集時間與應用程序時間的比率設置為1/(1+),默認值是99%(垃圾收集時間的1%)。
-Xmx:指定最大堆占用空間。
優先級保證:暫停時間>吞吐量>堆空間。如果不設置初始堆內存和最大堆內存,則初始堆大小為物理內存的1/64,最大內存為1/4,年輕代大小為堆內存的1/3。
-Xms (初始堆內存) and -Xmx (最大堆內存):
如果知道應用程序需要多少堆才能正常工作,那么可以將-Xms和-Xmx設置為相同的值。如果不知道,那么JVM將首先使用初始堆大小,然后自動增長,直到它找到堆使用和性能之間的平衡。
三個重要參數:1、-XX:MaxGCPauseTimeMillis:設置最大垃圾回收時間停頓時間。
2、-XX:GCTimeRatio:設置吞吐量大小。
3、-XX:UseAdaptiveSizePolicy:自適應模式。新生代大小,eden區與survivor區的比例,晉升老年代的對象年齡等參數會被自動調整。
CMS
使用方法:-XX:+UseConcMarkSweepGC
并發線程數:(ParallelGCThreads+3)/4。也可用通過-XX:ConcGCThreads或者-XX:ParallelCMSThreads手工設置。
因為并發性質,所以CMS不會等到堆飽和時才進行垃圾回收。默認值為老年代占用率68%,通過-XX:CMSInitiatingOccupancyFraction設置。
內存壓縮:設定多少次之后GC回收之后對內存進行一次壓縮。-XX:CMSFullGCsBeforeCompaction。默認0
開啟-XX:CMSClassUnloadingEnable,可以在需要時候Perm區的還會觸發一次FullGC。
6.2 G1
6.2.1 啟用G1(常用):-XX:+UseG1GC
堆內存(常用):-XX:InitialHeapSize(初始堆內存)-XX:MaxHeapSize(最大堆內存)
年輕代設置(常用):-XX:NewSize(最小) -XX:MaxNewSize(最大)
暫停時間(常用):-XX:MaxGCPauseTimeMillis=(默認200ms)
空閑堆占比:-XX:MinHeapFreeRatio=40(GC后,如果發現空閑堆內存占到整個預估堆內存的40%,則放大堆內存的預估最大值,但不超過固定最大值。)-XX:MaxHeapFreeRatio=70
最大暫停間隔時間:-XX:PauseTimeIntervalMillis
GC停頓時候的并行的GC收集線程數:-XX:ParallelGCThreads=< ergo>根據虛擬機所在的主機的可用CPU線程數來計算的:如果CPU少于8個這個值就是cpu的數量,否則,就等于cpu數量*5/8。每個停頓開始的時候,最大的GC線程數還受限于最大的堆內存,G1的內個線程能使用的最大堆內存是由-XX:HeapSizePerGCThread來設置的。
與應用并發執行的GC線程數:-XX:ConcGCThreads=< ergo>:默認是-XX:ParallelGCThreads/4
region的大小:-XX:G1HeapRegionSize=< ergo>整個堆大概有2048個region,region的大小可以在1-32M之間,必須是2的次方。調整之后會影響分配對象的大小及停頓時間。
可分配的最大對象的大小: -XX:G1HeapRegionSize-XX:G1MaxNewSizePercent
-------------------------------------------------------------
七、針對項目經驗集
-------------------------------------------------------------
7.1 這幾種GC收集器相比之下,只要JDK版本在1.7u4及以上,推薦使用G1收集器。JDK1.7,1.8都默認使用PS(并行收集器)
7.2 尤其注意容器項目,容器設置的JVM配置內存大小不能大于容器內存大小,否則參數配置無效。
7.3 調優實例
7.3.1 實例一(集團2018版XXXXXXXX系統):
壓測表現:穩定性壓測時結果不穩定且內存消耗一直80%左右,影響時間3天。
內存分析表現:堆內存很大(7G)但年輕代內存非常小,年輕代minGC頻繁,老年代內存一直增加直至觸發majorGC,且GC暫停時間長,平均200~300ms
調優:調整年輕代內存為3G
export JAVA_OPTS="$JAVA_OPTS -Xmx7g -Xms7g -XX:NewSize=3g -XX:MaxNewSize=3g -XX:+UseG1GC"
優化結果:
復壓,內存穩定在60%左右,年輕代GC頻繁度減小,GC耗時100ms左右,老年代穩定無majorGC
7.3.2 實例二(壽險2013版XXXXXXXX系統):
壓測表現:壓測時壓不上去,服務器消耗未滿載。且壓的時間長了TPS會有斷崖式下降,TPS和相應時間非常不穩定。影響時間2天。
內存分析表現:heap內存只設置了2G,容器是3.5C7G,老年代一直增長直至觸發majorGC,GC頻繁且GC暫停時間不穩定。
調優:調整heap內存大小7G,新生代3G。
JAVA_OPTS="-Xmx7000m -Xms7000m -Xmn3072m -XX:PermSize=256M -XX:MaxPermSize=256M
調優結果:
TPS增長40~50且穩定。老年代穩定無majorGC。minorGC頻繁度減小,GC暫停時間降低且穩定在幾十ms以內。
7.3.3 Tip1:年輕代內存并不是越大越好,雖然會減小GC的頻率,但是在GC時會增加回收時間造成GC暫停時間長。
Tip2:docker系統,如果容器內存4G,堆內存設置6G,進程可以啟動且顯示為6G,但實際只能使用到4G。
Tip3:如果開發和測試都不清楚如何設置堆大小及年輕代大小,可以參考perfma產品 http://xxfox.perfma.com/ ,填寫相關參數會給出調優建議
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的java_opts gc回收器_JVM之垃圾回收机制(GC)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java 检查pos机状态_POS机故障
- 下一篇: java怎么调用存储函数_java中调用