優(yōu)化gc的核心是減少頻率和停頓時(shí)間,通過理解jvm機(jī)制并調(diào)整參數(shù)實(shí)現(xiàn)。1.監(jiān)控gc日志,使用-xlog:gc*分析頻率、時(shí)間和原因;2.選擇合適回收器,如g1兼顧吞吐與停頓,zgc低延遲但資源消耗高;3.調(diào)整堆內(nèi)存大小,設(shè)置-xms和-xmx一致以避免性能損耗;4.調(diào)整新生代與老年代比例,合理設(shè)置newratio和survivorratio;5.代碼優(yōu)化,減少臨時(shí)對(duì)象創(chuàng)建,使用對(duì)象池和高效算法;6.分析日志定位瓶頸,關(guān)注full gc頻率及原因,使用工具如gceasy輔助分析;7.選擇合適的gc日志級(jí)別,權(quán)衡詳細(xì)性與性能損耗。
Java中優(yōu)化GC,說白了就是盡可能減少GC的頻率和每次GC的時(shí)間,讓你的應(yīng)用跑得更快更穩(wěn)定。核心在于理解JVM的GC機(jī)制,并根據(jù)你的應(yīng)用特點(diǎn)調(diào)整JVM參數(shù)。
解決方案
優(yōu)化GC是一個(gè)系統(tǒng)性的工程,不是一蹴而就的。首先要搞清楚你的應(yīng)用瓶頸在哪里,是頻繁的Minor GC,還是Full GC?然后針對(duì)性地調(diào)整參數(shù)。
立即學(xué)習(xí)“Java免費(fèi)學(xué)習(xí)筆記(深入)”;
- 監(jiān)控GC日志: 這是最基礎(chǔ)也是最重要的。用 -verbose:gc 或 -Xlog:gc* 開啟GC日志,分析GC的頻率、時(shí)間和原因。現(xiàn)在通常用 -Xlog:gc*,更詳細(xì)。
- 選擇合適的垃圾回收器: JVM提供了多種垃圾回收器,比如Serial GC, Parallel GC, cms, G1, ZGC, Shenandoah。不同的回收器適用于不同的場(chǎng)景。
- Serial GC: 單線程,適合單核CPU或者數(shù)據(jù)量小的應(yīng)用。
- Parallel GC: 多線程,適合多核CPU,吞吐量優(yōu)先。
- CMS: 關(guān)注停頓時(shí)間,但容易產(chǎn)生碎片。已經(jīng)被標(biāo)記為Deprecated,不建議使用。
- G1: JDK9之后默認(rèn)的垃圾回收器,兼顧吞吐量和停頓時(shí)間,適合大堆內(nèi)存的應(yīng)用。
- ZGC和Shenandoah: 更低的停頓時(shí)間,但對(duì)CPU和內(nèi)存消耗較高。
- 選擇哪個(gè)?沒有絕對(duì)的答案,需要根據(jù)你的應(yīng)用特點(diǎn)和資源情況進(jìn)行測(cè)試。一般來說,G1是一個(gè)不錯(cuò)的選擇。
- 調(diào)整堆內(nèi)存大小: 堆內(nèi)存太小,容易頻繁GC;堆內(nèi)存太大,F(xiàn)ull GC時(shí)間會(huì)很長。
- -Xms: 初始堆大小。
- -Xmx: 最大堆大小。
- 通常 -Xms 和 -Xmx 設(shè)置為相同的值,避免堆內(nèi)存動(dòng)態(tài)調(diào)整帶來的性能損耗。
- 調(diào)整新生代和老年代的比例: 新生代越大,Minor GC頻率越低,但老年代越小,F(xiàn)ull GC頻率越高。
- -XX:NewRatio: 設(shè)置新生代和老年代的比例。比如 -XX:NewRatio=2 表示老年代是新生代的2倍。
- -XX:NewSize: 設(shè)置新生代的初始大小。
- -XX:MaxNewSize: 設(shè)置新生代的最大大小。
- 調(diào)整Survivor區(qū)的大小: Survivor區(qū)太小,對(duì)象容易提前進(jìn)入老年代;Survivor區(qū)太大,浪費(fèi)空間。
- -XX:SurvivorRatio: 設(shè)置Eden區(qū)和一個(gè)Survivor區(qū)的比例。比如 -XX:SurvivorRatio=8 表示Eden區(qū)是Survivor區(qū)的8倍,也就是說每個(gè)Survivor區(qū)占新生代的1/10。
- 使用G1垃圾回收器的一些常用參數(shù):
- -XX:MaxGCPauseMillis: 設(shè)置最大GC停頓時(shí)間,G1會(huì)盡量滿足這個(gè)目標(biāo)。
- -XX:InitiatingHeapOccupancyPercent: 設(shè)置老年代使用多少比例時(shí)觸發(fā)并發(fā)GC。
- 代碼優(yōu)化:
- 減少臨時(shí)對(duì)象的創(chuàng)建: 盡量復(fù)用對(duì)象,避免在循環(huán)中創(chuàng)建大量對(duì)象。
- 使用對(duì)象池: 對(duì)于一些創(chuàng)建和銷毀代價(jià)比較大的對(duì)象,可以使用對(duì)象池來復(fù)用。
- 避免過大的對(duì)象: 過大的對(duì)象容易直接進(jìn)入老年代,增加Full GC的壓力。
- 及時(shí)釋放資源: 比如關(guān)閉IO流,釋放數(shù)據(jù)庫連接。
如何分析GC日志,找到性能瓶頸?
GC日志是診斷GC問題的關(guān)鍵。要學(xué)會(huì)看懂GC日志,才能找到性能瓶頸。
- 關(guān)注GC的類型: 是Minor GC還是Full GC?Full GC的代價(jià)遠(yuǎn)高于Minor GC。
- 關(guān)注GC的頻率: GC頻率過高,說明堆內(nèi)存不夠用,或者代碼中存在內(nèi)存泄漏。
- 關(guān)注GC的時(shí)間: GC時(shí)間過長,會(huì)影響應(yīng)用的響應(yīng)時(shí)間。
- 使用GC日志分析工具: 比如GCeasy、HeapHero等,可以更方便地分析GC日志。這些工具可以幫你可視化GC的各項(xiàng)指標(biāo),快速定位問題。
例如,如果你發(fā)現(xiàn)Full GC頻繁發(fā)生,可能是以下原因:
- 老年代空間不足: 增加老年代空間。
- 晉升失敗: 新生代的對(duì)象過早進(jìn)入老年代,導(dǎo)致老年代空間不足??梢試L試調(diào)整新生代和Survivor區(qū)的大小。
- 大對(duì)象: 大對(duì)象直接進(jìn)入老年代,導(dǎo)致老年代空間不足??梢試L試優(yōu)化代碼,避免創(chuàng)建過大的對(duì)象。
除了JVM參數(shù),還有哪些優(yōu)化GC的方法?
除了調(diào)整JVM參數(shù),還可以從代碼層面進(jìn)行優(yōu)化,減少GC的壓力。
- 使用高效的數(shù)據(jù)結(jié)構(gòu)和算法: 選擇合適的數(shù)據(jù)結(jié)構(gòu)和算法可以減少內(nèi)存占用和對(duì)象創(chuàng)建。比如使用 StringBuilder 代替 String 進(jìn)行字符串拼接。
- 使用緩存: 緩存可以減少對(duì)數(shù)據(jù)庫或外部服務(wù)的訪問,降低系統(tǒng)負(fù)載。
- 使用異步處理: 將一些耗時(shí)的操作放到異步線程中執(zhí)行,避免阻塞主線程。
- 使用連接池: 數(shù)據(jù)庫連接池和線程池可以復(fù)用連接和線程,減少創(chuàng)建和銷毀的開銷。
- 避免內(nèi)存泄漏: 內(nèi)存泄漏會(huì)導(dǎo)致堆內(nèi)存不斷增長,最終觸發(fā)Full GC。可以使用內(nèi)存分析工具,比如MAT (Memory Analyzer Tool) 來檢測(cè)內(nèi)存泄漏。
如何選擇合適的GC日志級(jí)別?
GC日志級(jí)別從最簡(jiǎn)略到最詳細(xì),選擇哪個(gè)級(jí)別取決于你想要診斷的問題。
- -verbose:gc 或 -Xlog:gc: 最基本的GC日志,只打印GC的類型、時(shí)間和堆內(nèi)存使用情況。適合初步了解GC情況。
- -Xlog:gc,gc+age=trace: 打印每次GC后,對(duì)象的年齡分布情況??梢杂脕矸治鰧?duì)象是否過早進(jìn)入老年代。
- -Xlog:gc+phases=trace: 打印GC的每個(gè)階段的耗時(shí)??梢杂脕矸治鯣C的瓶頸在哪里。
- -Xlog:gc+heap=trace: 打印每次GC前后,堆內(nèi)存的詳細(xì)使用情況。
- -Xlog:gc+metaspace=trace: 打印Metaspace的使用情況。Metaspace是用來存儲(chǔ)類元數(shù)據(jù)的,如果Metaspace空間不足,也會(huì)觸發(fā)GC。
通常情況下,-Xlog:gc* 已經(jīng)足夠詳細(xì)了。如果需要更深入的分析,可以嘗試其他的GC日志級(jí)別。但是,更詳細(xì)的GC日志也會(huì)帶來一定的性能損耗,需要根據(jù)實(shí)際情況進(jìn)行權(quán)衡。