java垃圾回收文档整理
GC和GC Tuning
GC的基礎知識
1.什么是垃圾
C語言申請內存:malloc free
C++: new delete
c/C++ 手動回收內存
Java: new ?
自動內存回收,編程上簡單,系統不容易出錯,手動釋放內存,容易出兩種類型的問題:
沒有任何引用指向的一個對象或者多個對象(循環引用)
2.如何定位垃圾
3.常見的垃圾回收算法
4.JVM內存分代模型(用于分代垃圾回收算法)
部分垃圾回收器使用的模型
除Epsilon ZGC Shenandoah之外的GC都是使用邏輯分代模型
G1是邏輯分代,物理不分代
除此之外不僅邏輯分代,而且物理分代
新生代 + 老年代 + 永久代(1.7)Perm Generation/ 元數據區(1.8) Metaspace
新生代 = Eden + 2個suvivor區
老年代
GC Tuning (Generation)
對象分配過程圖
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ziWi71d6-1610417589879)(對象分配過程詳解.png)]
動態年齡:(不重要)
https://www.jianshu.com/p/989d3b06a49d
分配擔保:(不重要)
YGC期間 survivor區空間不夠了 空間擔保直接進入老年代
參考:https://cloud.tencent.com/developer/article/1082730
5.常見的垃圾回收器
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ZxnBfv92-1610417589882)(常用垃圾回收器.png)]
從分代算法演化到不分代算法
Serial算法 幾十兆
Parallel算法 幾個G
CMS 幾十個G - 承上啟下,開始并發回收 -
.- 三色標記 -
并發垃圾回收是因為無法忍受STW
CMS問題比較多,所以現在沒有一個版本默認是CMS,只能手工指定
CMS既然是MarkSweep,就一定會有碎片化的問題,碎片到達一定程度,CMS的老年代分配對象分配不下的時候,使用SerialOld 進行老年代回收
想象一下:
PS + PO -> 加內存 換垃圾回收器 -> PN + CMS + SerialOld(幾個小時 - 幾天的STW)
幾十個G的內存,單線程回收 -> G1 + FGC 幾十個G -> 上T內存的服務器 ZGC
算法:三色標記 + Incremental Update
算法:三色標記 + SATB
算法:ColoredPointers + LoadBarrier
算法:ColoredPointers + WriteBarrier
?https://docs.oracle.com/en/java/javase/13/gctuning/ergonomics.html#GUID-3D0BB91E-9BFF-4EBB-B523-14493A860E73
1.8默認的垃圾回收:PS + ParallelOld
常見垃圾回收器組合參數設定:(1.8)
-
-XX:+UseSerialGC = Serial New (DefNew) + Serial Old
- 小型程序。默認情況下不會是這種選項,HotSpot會根據計算及配置和JDK版本自動選擇收集器
-
-XX:+UseParNewGC = ParNew + SerialOld
- 這個組合已經很少用(在某些版本中已經廢棄)
- https://stackoverflow.com/questions/34962257/why-remove-support-for-parnewserialold-anddefnewcms-in-the-future
-
-XX:+UseConc(urrent)MarkSweepGC = ParNew + CMS + Serial Old
-
-XX:+UseParallelGC = Parallel Scavenge + Parallel Old (1.8默認) 【PS + SerialOld】
-
-XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old
-
-XX:+UseG1GC = G1
-
Linux中沒找到默認GC的查看方法,而windows中會打印UseParallelGC
- java +XX:+PrintCommandLineFlags -version
- 通過GC的日志來分辨
-
Linux下1.8版本默認的垃圾回收器到底是什么?
- 1.8.0_181 默認(看不出來)Copy MarkCompact
- 1.8.0_222 默認 PS + PO
JVM調優第一步,了解JVM常用命令行參數
-
JVM的命令行參數參考:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
-
HotSpot參數分類
標準: - 開頭,所有的HotSpot都支持
非標準:-X 開頭,特定版本HotSpot支持特定命令
不穩定:-XX 開頭,下個版本可能取消
java -version
java -X
java -XX:+PrintFlagsWithComments //只有debug版本能用
試驗用程序:
import java.util.List; import java.util.LinkedList;public class HelloGC {public static void main(String[] args) {System.out.println("HelloGC!");List list = new LinkedList();for(;;) {byte[] b = new byte[1024*1024];list.add(b);}} } - 區分概念:內存泄漏memory leak,內存溢出out of memory
- java -XX:+PrintCommandLineFlags HelloGC
- java -Xmn10M -Xms40M -Xmx60M -XX:+PrintCommandLineFlags -XX:+PrintGC HelloGC
PrintGCDetails PrintGCTimeStamps PrintGCCauses - java -XX:+UseConcMarkSweepGC -XX:+PrintCommandLineFlags HelloGC
- java -XX:+PrintFlagsInitial 默認參數值
- java -XX:+PrintFlagsFinal 最終參數值
- java -XX:+PrintFlagsFinal | grep xxx 找到對應的參數
- java -XX:+PrintFlagsFinal -version |grep GC
- java -XX:+PrintFlagsFinal -version | wc -l
共728個參數
PS GC日志詳解
每種垃圾回收器的日志格式是不同的!
PS日志格式
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-WZSS1n43-1610417589883)(./GC日志詳解.png)]
heap dump部分:
eden space 5632K, 94% used [0x00000000ff980000,0x00000000ffeb3e28,0x00000000fff00000)后面的內存地址指的是,起始地址,使用空間結束地址,整體空間結束地址[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-kNdMfUmd-1610417589886)(GCHeapDump.png)]
total = eden + 1個survivor
調優前的基礎概念:
所謂調優,首先確定,追求啥?吞吐量優先,還是響應時間優先?還是在滿足一定的響應時間的情況下,要求達到多大的吞吐量…
問題:
科學計算,吞吐量。數據挖掘,thrput。吞吐量優先的一般:(PS + PO)
響應時間:網站 GUI API (1.8 G1)
什么是調優?
調優,從規劃開始
-
調優,從業務場景開始,沒有業務場景的調優都是耍流氓
-
無監控(壓力測試,能看到結果),不調優
-
步驟:
- 熟悉業務場景(沒有最好的垃圾回收器,只有最合適的垃圾回收器)
- 響應時間、停頓時間 [CMS G1 ZGC] (需要給用戶作響應)
- 吞吐量 = 用戶時間 /( 用戶時間 + GC時間) [PS]
- 選擇回收器組合
- 計算內存需求(經驗值 1.5G 16G)
- 選定CPU(越高越好)
- 設定年代大小、升級年齡
- 設定日志參數
- -Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause
- 或者每天產生一個日志文件
- 觀察日志情況
-
案例1:垂直電商,最高每日百萬訂單,處理訂單系統需要什么樣的服務器配置?
這個問題比較業余,因為很多不同的服務器配置都能支撐(1.5G 16G)
1小時360000集中時間段, 100個訂單/秒,(找一小時內的高峰期,1000訂單/秒)
經驗值,
非要計算:一個訂單產生需要多少內存?512K * 1000 500M內存
專業一點兒問法:要求響應時間100ms
壓測!
-
案例2:12306遭遇春節大規模搶票應該如何支撐?
12306應該是中國并發量最大的秒殺網站:
號稱并發量100W最高
CDN -> LVS -> NGINX -> 業務系統 -> 每臺機器1W并發(10K問題) 100臺機器
普通電商訂單 -> 下單 ->訂單系統(IO)減庫存 ->等待用戶付款
12306的一種可能的模型: 下單 -> 減庫存 和 訂單(redis kafka) 同時異步進行 ->等付款
減庫存最后還會把壓力壓到一臺服務器
可以做分布式本地庫存 + 單獨服務器做庫存均衡
大流量的處理方法:分而治之
-
怎么得到一個事務會消耗多少內存?
-
弄臺機器,看能承受多少TPS?是不是達到目標?擴容或調優,讓它達到
-
用壓測來確定
優化環境
的堆,用戶反饋網站比較緩慢,因此公司決定升級,新的服務器為64位,16G
的堆內存,結果用戶反饋卡頓十分嚴重,反而比以前效率更低了
很多用戶瀏覽數據,很多數據load到內存,內存不足,頻繁GC,STW長,響應時間變慢
內存越大,FGC時間越長
PS -> PN + CMS 或者 G1
CPU100%那么一定有線程在占用系統資源,
解決JVM運行中的問題
一個案例理解常用工具
測試代碼:
package com.mashibing.jvm.gc;import java.math.BigDecimal; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit;/*** 從數據庫中讀取信用數據,套用模型,并把結果進行記錄和傳輸*/public class T15_FullGC_Problem01 {private static class CardInfo {BigDecimal price = new BigDecimal(0.0);String name = "張三";int age = 5;Date birthdate = new Date();public void m() {}}private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(50,new ThreadPoolExecutor.DiscardOldestPolicy());public static void main(String[] args) throws Exception {executor.setMaximumPoolSize(50);for (;;){modelFit();Thread.sleep(100);}}private static void modelFit(){List<CardInfo> taskList = getAllCardInfo();taskList.forEach(info -> {// do somethingexecutor.scheduleWithFixedDelay(() -> {//do sth with infoinfo.m();}, 2, 3, TimeUnit.SECONDS);});}private static List<CardInfo> getAllCardInfo(){List<CardInfo> taskList = new ArrayList<>();for (int i = 0; i < 100; i++) {CardInfo ci = new CardInfo();taskList.add(ci);}return taskList;} }java -Xms200M -Xmx200M -XX:+PrintGC com.mashibing.jvm.gc.T15_FullGC_Problem01
一般是運維團隊首先受到報警信息(CPU Memory)
top命令觀察到問題:內存不斷增長 CPU占用率居高不下
top -Hp 觀察進程中的線程,哪個線程CPU和內存占比高
jps定位具體java進程
jstack 定位線程狀況,重點關注:WAITING BLOCKED
eg.
waiting on <0x0000000088ca3310> (a java.lang.Object)
假如有一個進程中100個線程,很多線程都在waiting on ,一定要找到是哪個線程持有這把鎖
怎么找?搜索jstack dump的信息,找 ,看哪個線程持有這把鎖RUNNABLE
作業:1:寫一個死鎖程序,用jstack觀察 2 :寫一個程序,一個線程持有鎖不釋放,其他線程等待
為什么阿里規范里規定,線程的名稱(尤其是線程池)都要寫有意義的名稱
怎么樣自定義線程池里的線程名稱?(自定義ThreadFactory)
jinfo pid
jstat -gc 動態觀察gc情況 / 閱讀GC日志發現頻繁GC / arthas觀察 / jconsole/jvisualVM/ Jprofiler(最好用)
jstat -gc 4655 500 : 每個500個毫秒打印GC的情況
如果面試官問你是怎么定位OOM問題的?如果你回答用圖形界面(錯誤)
1:已經上線的系統不用圖形界面用什么?(cmdline arthas)
2:圖形界面到底用在什么地方?測試!測試的時候進行監控!(壓測觀察)
jmap - histo 4655 | head -20,查找有多少對象產生
jmap -dump:format=b,file=xxx pid :
線上系統,內存特別大,jmap執行期間會對進程產生很大影響,甚至卡頓(電商不適合)
1:設定了參數HeapDump,OOM的時候會自動產生堆轉儲文件(不是很專業,因為多有監控,內存增長就會報警)
2:很多服務器備份(高可用),停掉這臺服務器對其他服務器不影響
3:在線定位(一般小點兒公司用不到)
4:在測試環境中壓測(產生類似內存增長問題,在堆還不是很大的時候進行轉儲)
java -Xms20M -Xmx20M -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError com.mashibing.jvm.gc.T15_FullGC_Problem01
使用MAT / jhat /jvisualvm 進行dump文件分析
https://www.cnblogs.com/baihuitestsoftware/articles/6406271.html
jhat -J-mx512M xxx.dump
http://192.168.17.11:7000
拉到最后:找到對應鏈接
可以使用OQL查找特定問題對象
找到代碼的問題
jconsole遠程連接
程序啟動加入參數:
java -Djava.rmi.server.hostname=192.168.17.11 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=11111 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false XXX如果遭遇 Local host name unknown:XXX的錯誤,修改/etc/hosts文件,把XXX加入進去
192.168.17.11 basic localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6關閉linux防火墻(實戰中應該打開對應端口)
service iptables stop chkconfig iptables off #永久關閉windows上打開 jconsole遠程連接 192.168.17.11:11111
jvisualvm遠程連接
https://www.cnblogs.com/liugh/p/7620336.html (簡單做法)
jprofiler (收費)
arthas在線排查工具
- 為什么需要在線排查?
在生產上我們經常會碰到一些不好排查的問題,例如線程安全問題,用最簡單的threaddump或者heapdump不好查到問題原因。為了排查這些問題,有時我們會臨時加一些日志,比如在一些關鍵的函數里打印出入參,然后重新打包發布,如果打了日志還是沒找到問題,繼續加日志,重新打包發布。對于上線流程復雜而且審核比較嚴的公司,從改代碼到上線需要層層的流轉,會大大影響問題排查的進度。 - jvm觀察jvm信息
- thread定位線程問題
- dashboard 觀察系統情況
- heapdump + jhat分析
- jad反編譯
動態代理生成類的問題定位
第三方的類(觀察代碼)
版本問題(確定自己最新提交的版本是不是被使用) - redefine 熱替換
目前有些限制條件:只能改方法實現(方法已經運行完成),不能改方法名, 不能改屬性
m() -> mm() - sc - search class
- watch - watch method
- 沒有包含的功能:jmap
GC算法的基礎概念
- Card Table
由于做YGC時,需要掃描整個OLD區,效率非常低,所以JVM設計了CardTable, 如果一個OLD區CardTable中有對象指向Y區,就將它設為Dirty,下次掃描時,只需要掃描Dirty Card
在結構上,Card Table用BitMap來實現
CMS
CMS的問題
Memory Fragmentation
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction 默認為0 指的是經過多少次FGC才進行壓縮
Floating Garbage
Concurrent Mode Failure
產生:if the concurrent collector is unable to finish reclaiming the unreachable objects before the tenured generation fills up, or if an allocation cannot be satisfiedwith the available free space blocks in the tenured generation, then theapplication is paused and the collection is completed with all the applicationthreads stopped
解決方案:降低觸發CMS的閾值
PromotionFailed
解決方案類似,保持老年代有足夠的空間
–XX:CMSInitiatingOccupancyFraction 92% 可以降低這個值,讓CMS保持老年代足夠的空間
CMS日志分析
執行命令:java -Xms20M -Xmx20M -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC com.mashibing.jvm.gc.T15_FullGC_Problem01
[GC (Allocation Failure) [ParNew: 6144K->640K(6144K), 0.0265885 secs] 6585K->2770K(19840K), 0.0268035 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
ParNew:年輕代收集器
6144->640:收集前后的對比
(6144):整個年輕代容量
6585 -> 2770:整個堆的情況
(19840):整個堆大小
[GC (CMS Initial Mark) [1 CMS-initial-mark: 8511K(13696K)] 9866K(19840K), 0.0040321 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] //8511 (13696) : 老年代使用(最大)//9866 (19840) : 整個堆使用(最大) [CMS-concurrent-mark-start] [CMS-concurrent-mark: 0.018/0.018 secs] [Times: user=0.01 sys=0.00, real=0.02 secs] //這里的時間意義不大,因為是并發執行 [CMS-concurrent-preclean-start] [CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] //標記Card為Dirty,也稱為Card Marking [GC (CMS Final Remark) [YG occupancy: 1597 K (6144 K)][Rescan (parallel) , 0.0008396 secs][weak refs processing, 0.0000138 secs][class unloading, 0.0005404 secs][scrub symbol table, 0.0006169 secs][scrub string table, 0.0004903 secs][1 CMS-remark: 8511K(13696K)] 10108K(19840K), 0.0039567 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] //STW階段,YG occupancy:年輕代占用及容量//[Rescan (parallel):STW下的存活對象標記//weak refs processing: 弱引用處理//class unloading: 卸載用不到的class//scrub symbol(string) table: //cleaning up symbol and string tables which hold class-level metadata and //internalized string respectively//CMS-remark: 8511K(13696K): 階段過后的老年代占用及容量//10108K(19840K): 階段過后的堆占用及容量[CMS-concurrent-sweep-start] [CMS-concurrent-sweep: 0.005/0.005 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] //標記已經完成,進行并發清理 [CMS-concurrent-reset-start] [CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]//重置內部結構,為下次GC做準備G1
G1日志詳解
[GC pause (G1 Evacuation Pause) (young) (initial-mark), 0.0015790 secs] //young -> 年輕代 Evacuation-> 復制存活對象 //initial-mark 混合回收的階段,這里是YGC混合老年代回收[Parallel Time: 1.5 ms, GC Workers: 1] //一個GC線程[GC Worker Start (ms): 92635.7][Ext Root Scanning (ms): 1.1][Update RS (ms): 0.0][Processed Buffers: 1][Scan RS (ms): 0.0][Code Root Scanning (ms): 0.0][Object Copy (ms): 0.1][Termination (ms): 0.0][Termination Attempts: 1][GC Worker Other (ms): 0.0][GC Worker Total (ms): 1.2][GC Worker End (ms): 92636.9][Code Root Fixup: 0.0 ms][Code Root Purge: 0.0 ms][Clear CT: 0.0 ms][Other: 0.1 ms][Choose CSet: 0.0 ms][Ref Proc: 0.0 ms][Ref Enq: 0.0 ms][Redirty Cards: 0.0 ms][Humongous Register: 0.0 ms][Humongous Reclaim: 0.0 ms][Free CSet: 0.0 ms][Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 18.8M(20.0M)->18.8M(20.0M)][Times: user=0.00 sys=0.00, real=0.00 secs] //以下是混合回收其他階段 [GC concurrent-root-region-scan-start] [GC concurrent-root-region-scan-end, 0.0000078 secs] [GC concurrent-mark-start] //無法evacuation,進行FGC [Full GC (Allocation Failure) 18M->18M(20M), 0.0719656 secs][Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 18.8M(20.0M)->18.8M(20.0M)], [Metaspace: 38 76K->3876K(1056768K)] [Times: user=0.07 sys=0.00, real=0.07 secs]案例匯總
OOM產生的原因多種多樣,有些程序未必產生OOM,不斷FGC(CPU飆高,但內存回收特別少) (上面案例)
GC常用參數
Parallel常用參數
CMS常用參數
G1常用參數
作業
-XX:MaxTenuringThreshold控制的是什么?
生產環境中,傾向于將最大堆內存和最小堆內存設置為:(為什么?)
JDK1.8默認的垃圾回收器是:
什么是響應時間優先?
什么是吞吐量優先?
ParNew和PS的區別是什么?
ParNew和ParallelOld的區別是什么?(年代不同,算法不同)
長時間計算的場景應該選擇:A:停頓時間 B: 吞吐量
大規模電商網站應該選擇:A:停頓時間 B: 吞吐量
HotSpot的垃圾收集器最常用有哪些?
常見的HotSpot垃圾收集器組合有哪些?
JDK1.7 1.8 1.9的默認垃圾回收器是什么?如何查看?
所謂調優,到底是在調什么?
如果采用PS + ParrallelOld組合,怎么做才能讓系統基本不產生FGC
如果采用ParNew + CMS組合,怎樣做才能夠讓系統基本不產生FGC
G1是否分代?G1垃圾回收器會產生FGC嗎?
如果G1產生FGC,你應該做什么?
問:生產環境中能夠隨隨便便的dump嗎?
問:常見的OOM問題有哪些?
如果JVM進程靜悄悄退出怎么辦?
如何排查直接內存?
有哪些常用的日志分析工具?
CPU暴增如何排查?
死鎖如何排查?
作者:馬士兵教育 http://mashibing.com
總結
以上是生活随笔為你收集整理的java垃圾回收文档整理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【原创】Android之修改AlertD
- 下一篇: Dcloud HTML5 监听蓝牙设备