一、預備知識
- 掌握GC相關的VM參數,會基本的空間調整
- 掌握相關工具
- 明白一點:調優跟應用、環境有關,沒有放之四海而皆準的法則
二、調優領域
- 內存
- 鎖競爭
- cpu占用
- io
三、確定目標
- 【低延遲】:CMS、G1(低延遲、高吞吐)、ZGC(jdk12體驗) 垃圾回收器
- 【高吞吐量】:ParallelGC 垃圾回收器
四、FullGC前后的內存占用,考慮下面幾個問題
- 數據是不是太多?
- resultSet = statement.sexecuteQuery(“select * from 大表”); 大量數據加載到堆內存,扛不住
- 數據表示是否臃腫?
- 對象圖:查詢時只查用到的字段
- 對象大小:new Object() 都要占16個字節。Integer 24 int 4,如果內存占用過多,可以考慮從一些對象上進行瘦身,能用基本類型的就不用包裝類型
- 是否存在內存泄漏?
- static Map map = ,靜態的Map,不斷向里面put數據,有可能導致溢出
- 可以加上軟、弱引用,
- 緩存可以使用第三方緩存實現,盡量不要使用static Map map
五、新生代調優
- 新生代特點
- 所有的new操作的內存分配非常廉價。
- 死亡對象的回收代價是零
- 大部分對象用過即死
- Minor GC的時間遠遠低于Full GC
- 新生代越大越好嗎?
- 新生代空間大了,老年代空間就小了。很容易觸發Full GC,暫停時間要比Minor GC暫停時間要長。
- 新生代空間建議在四分之一以上二分之一以下
- 晉升閾值配置得當,讓長時間存活對象盡快晉升
六、老年代調優
以CMS為例
- CMS的老年代內存越大越好
- 先嘗試不做調優,如果沒有Full GC那么已經很優了,否則先嘗試調優新生代
- 觀察發生Full GC時老年代內存占用,將老年代內存預設調大1/4~1/3
- -XX:CMSInitiatingOccupancyFraction=percent
七、案例
- Full GC和Minor GC頻繁
- 分析:說明空間緊張,如果是新生代空間緊張,當我們業務高峰期來了,大量對象被創建,很快新生代空間塞滿了(幸存區空間緊張了),對象的晉升閾值就會降低,本來生存周期很短的對象,會被晉升到老年代中,進一步出發老年代垃圾回收導致Full GC頻繁發生。
- 解決:通過檢查工具檢查堆空間大小,確實發現新生代空間太小了,試著增大新生代內存(增加幸存區空間,增大晉升閾值),這樣使一些生命周期較短的對象盡可能留在新生代,而不進入老年代,進而減少了老年代Full GC的發生
- 請求高峰期發生Full GC,單次暫停時間特別長(CMS)
- 分析:到底是哪部分暫停時間特別長,查看GC日志,初始標記和并發標記都是比較快的,耗時的時重新標記(CMS在重新標記的時候會掃描整個的堆內存:需要掃描老年代和新生代內存),由于高峰期,新生代可能存在大量對象,導致重新標記會很慢。
- 解決:在重新標記之前,可以先對新生代做一次垃圾回收,減少新生代對象數量,再次重新標記的時候,速度就快了
- 老年代充裕情況下,發生Full GC(CMS jdk1.7)
- 分析:導致Full GC的原因:1.空間不足,導致并發失敗。2.空間碎片比較多。從CG日志看沒有并發失敗或者碎片過多導致的提示。說明老年代內存充裕。那就應該是jdk版本問題,1.7及以前版本,永久帶空間不足,就會導致整個堆的一次Full GC出現,1.8及之后,改成了元空間(操作系統的內存空間),
- 解決:增加永久帶的內存空間