jstat -gcutil 输出结果分析_JVM故障分析

本文介绍了JVM故障分析,包括jstat、jmap、jstack等工具的使用,以及如何应对频繁FullGC和OOM问题。通过模拟压测和优化JVM参数,减少FullGC频率。同时,分析了OOM的不同类型,如堆内存溢出,借助MAT工具进行dump分析。强调了在上线前做好JVM调优和代码优化的重要性。
摘要由CSDN通过智能技术生成

在上一篇文章《JVM实战优化篇》中,梳理了JVM内存的核心参数,同时对新业务系统上线如何预估容量、垃圾回收器如何选择进行了总结,最后文末还给大家总结了一套通用的JVM参数模板。JVM的调优基本都发生在上线前,尽量争取在压测环节就可以把JVM参数调整到最优,最有效的优化手段是架构和代码层面的优化,如果上线以后发生了JVM故障,最常见的比如频繁FullGC导致CPU飙升、系统响应迟钝,OOM导致的服务不可用等场景,下面就工具和思路进行总结。

c8ead0eb2ffad305bccde38eaffb538a.png

                               大纲

一、JVM命令


1.jps,虛擬機進程狀況

./jps直接輸出各個JVM進程的PID。

5870069aad61a9a77900d86caea2e2a2.png

2.jstat,虛擬機統計信息監視工具

jstat命令可以查看堆內存各部分的使用量,以及加載類的數量。主要常用這個查看堆的使用情況:

jstat -gc pid

pid是VM的進程號,即當前運行的java進程號,後面還可以加幾個參數。

  • interval:執行的間隔時間,單位為毫秒

  • count :打印次數,如果缺省則一直打印

3bf118b4f6c9303c1919c625babb5aa2.png

                    使用jstat -gc vmid

上面的縮寫解釋:E-eden, S-suviror, M-metaspace, C-capacity, U-used 前面均为瞬时统计容量(byte)
以下统计了从应用程序启动到采样时的统计综合YGC :年轻代中gc次数YGCT :年轻代中gc所用时间(s)FGC:old代(全gc)gc次数FGCT:(全gc)gc所用时间(s)GCT:gc用的总时间(s)

3.jmap 虛擬機內存映像工具

jmap命令是一個可以輸出所有內存中對象的工具,甚至可以將VM 中的heap,以二進制輸出成文本。

  • 堆转储文件:jmap -dump:live,format=b,file=heap-dump.bin

  • 输出所有内存中对象统计,jmap -gc pid,输入的demo如下:

./jmap -heap 5932
Attaching to process ID 5932, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.91-b15

using thread-local object allocation.
Parallel GC with 4 thread(s)

Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 1073741824 (1024.0MB)
NewSize = 42991616 (41.0MB)
MaxNewSize = 357564416 (341.0MB)
OldSize = 87031808 (83.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)

Heap Usage:
PS Young Generation
Eden Space:
capacity = 60293120 (57.5MB)
used = 44166744 (42.120689392089844MB)
free = 16126376 (15.379310607910156MB)
73.25337285580842% used
From Space:
capacity = 5242880 (5.0MB)
used = 0 (0.0MB)
free = 5242880 (5.0MB)
0.0% used
To Space:
capacity = 14680064 (14.0MB)
used = 0 (0.0MB)
free = 14680064 (14.0MB)
0.0% used
PS Old Generation
capacity = 120061952 (114.5MB)
used = 19805592 (18.888084411621094MB)
free = 100256360 (95.6119155883789MB)
16.496143590935453% used

20342 interned Strings occupying 1863208 bytes.
4.jstack 虚拟机堆栈跟踪工具

jstack是JDK自帶的線程跟蹤工具,用於打印指定Java進程的線程堆棧信息。常用於排查cpu使用率過高的問題,其排查思路也比較套路:
1、通过top命令找出cpu使用率最高的进程
2、然后通过top -Hp pid查看该进程下各个线程的cpu使用情况
3、继续使用jstack pid命令查看当前java进程的堆栈状态,且将内容输出到dump文件中。
4、将PID(十进制)转换成十六进制后,用转换后的数字去定位线程的一个执行状态和堆栈信息。
5、也可以使用jca工具分析线程dump文件,找出来线程处于BLOCKED和WAITING状态的,并进行堆栈分析

f52e9d21d13d615ef87e4109a5ed4b8f.png

                                   dump分析

可以重點懷疑那種應該很快執行但是並未執行的線程,比如dubbo調用等。

  • RUNNABLE,线程处于执行中

  • BLOCKED,线程被阻塞

  • WAITING,线程正在等待

二、频繁FullGC实战


FullGC發生時一般會導致CPU升高、系統響應變長,服務會短暫停頓等現象,如果頻繁的發生FGC的話,那麼就會導致系統的服務處於一個不穩定的狀況,引發FullGC的原因會很多,我們盡量要做的就是在上線之前,做好壓測,避免JVM參數的不合理導致頻繁FGC。

1.模拟压测

模擬一個壓測場景,頻繁的創建對象,然後又很快的回收處理,分析步驟如下:

  • 每秒执行一次loadData()方法

  • 创建了40M的字节数组指向data变量,但是马上成为垃圾;

  • data1和data2指向两个10M数组,有引用而可以存活

  • data3依次指向了两个10M数组

public class FullGCOptmize {
public static void main(String[] args) throws Exception{
Thread.sleep(1000);
while (true) {
loadData();
}
}
public static void loadData() throws Exception{
byte[] data = null;
for (int i = 0; i < 4; i++) {
data = new byte[10 * 1024 * 1024];
}
data = null;
byte[] data1 = new byte[10 * 1024 * 1024];
byte[] data2 = new byte[10 * 1024 * 1024];
byte[] data3 = new byte[10 * 1024 * 1024];
data3 = new byte[10 * 1024 * 1024];
Thread.sleep(1000);
}
}

此處設定的一個不太合理的JVM參數:

-Xms200M -Xmx200M -XX:NewSize=100m
-XX:MaxNewSize=100m -XX:SurvivorRatio=8
-XX:PretenureSizeThreshold=20m -XX:+UseConcMarkSweepGC
-Xloggc:gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps

100M新生代,Eden區80M,S區10M,Old區100M,1s內執行完loadData()方法產生80M垃圾,一定會觸發一次MinorGC。所以我們用jstat命令監控會發现:

  • 第一次ygc,30M数组(data1、2、3)直接进入老年代(OU),data和data3的数组对象直接被回收。

  • 后续每秒一次的ygc,都会有大约10~30M进入老年代(OU)

  • 大约在60M的时候,发生了一次老年代GC,老年代的内存使用从60M->30M。

  • ygc太频繁,导致每次回收后存活对象太多,频繁进入老年代,频繁触发FullGC。

c4c5b19bc9029a6acc49f2df5181049f.png

                          使用jstat -gc vmid

上面採用的jstat可以準確的去觀察數值上的精確變化,但是如果想利用可視化的工具,觀察一個總的盤面情況,可以採用VisualGC工具:

09579dad4368382eced882740ce4960e.png

                        visualGC观察GC情况

2.优化思路

優化思路:擴大新生代,擴大Survivor區空間,堆大小300M,其中新生代擴大至200M(Eden區100M,S區各50M)。JVM參數設定如下:

-Xms300M -Xmx300M -XX:NewSize=200m -XX:MaxNewSize=200m
-XX:SurvivorRatio=2 -XX:PretenureSizeThreshold=20m
-XX:+UseConcMarkSweepGC -Xloggc:gc.log
-XX:+PrintGCDetails -XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps

此時再次觀察GC頻率和耗時每秒新增80M,每次ygc後有10M對象進入survivor區,8次ygc後才有600k數據進入老年代,FullGC已經完全消失。

8945882c305186fa0cd56f7cfa7e5a26.png

                      jstatc观察gc情况

9a4d76cad9bf77e8c22f70e830260c16.png

                       visualGC统计情况

3.FullGC原因及思路总结

上線前要充分做好壓測和JVM調優,這樣在線上就不會因為參數不合理而導致正常的業務運行而觸發了FullGC的情況。這裡調優的思路仍然遵循前面文章中的方案來進行:

  • 让短命对象在MinorGC阶段就被回收;

  • MinorGC后的存活对象

  • 长命对象尽早进入老年代,不要在新生代来回复制;

參數已經優化完畢,且上線一段時間以後,如果還會出現FullGC頻繁或者時間過長,分析思路就要從JVM調優轉化到架構和代碼調優上去,且需要一些特殊手段去排查GC問題,尤其需要結合dump分析來解決,首先要抓住FullGC時前後的堆dump日誌,然後再利用MAT工具進行堆棧分析和對象分布分析,這個工具的使用後面會繼續講到。這種層面的故障分析就需要結合業務+代碼一起聯合分析,而不是再去調整JVM參數能夠!

706e222c91ccc0b2cd705e852c95a919.png

                            FullGC原因及思路

如果FullGC无法准确的去抓取快照dump,那么还是可以在JVM启动的时候增加下面两个参数-XX:+HeapDumpBeforeFullGC -XX:+HeapDumpAfterFullGC就可以在FGC的前后dump一下内存。不过增加这两个参数会在每次FGC的时候dump内存,dump内存本身对应用有影响,但是不会导致JVM停机。特别是在web应用启动的时候,就会不断的去dump内存,因为web应用启动的时候会频繁发生FGC,所以这个命令需要慎用。

三、OOM实战


1.OOM类型及原因

之前的FullGC尚只是對系統的性能和使用造成一定的衝擊,影響至少可控,但是假如發生了OOM,那就相當於系統掛掉一樣,內存溢出無法再分配對象空間,那也就意味著系統無法接受任何請求,這個直接就是生產事故級別了。通常要分析OOM事件,就少不了要去排查內存洩漏的問題。內存洩漏是因,內存溢出是果:

  • 内存泄露:申请使用完的内存没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了,因为申请者不用了,而又不能被虚拟机分配给别人用。

  • 内存溢出:申请的内存超出了JVM能提供的内存大小,此时称之为溢出。

能发生OOM的地方通常是四个:堆内,堆外,元空间(jdk8以前叫老年代)和虚拟机栈

2.heap dump分析

为了能够在OOM发生的时候能留下现场证据,线上服务的JVM参数中一般需要配置-XX:+HeapDumpOnOutOfMemoryError,在发生OOM之前将堆中对象和内存dump一份快照出来。我们这里模拟一个触发OOM的场景,非常简单,即无线的创造对象且不被回收,直到JVM内存打爆触发OOM:

public class HeapOOM {
static class OOMObject {
}
public static void main(String[] args) {
int i = 0;
Listlist = new ArrayList();
cycle(list, i);
}public static void cycle(Listlist, int i) {//在堆中无限创建对象while (true) {
System.out.println("already create objects=" + i++);list.add(new OOMObject());
}
}
}

同时设置一个JVM参数如下:

-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:/dump/

执行主程序以后,没过几秒函数就挂掉了,此时我们拿出堆MAT dump工具进行分析:

1、查看对象分布饼图,一般选择前面几个大对象进行分析

6430d41fab7c690eab1d073304b6e6d5.png

2、GC Roots线程追踪 和 被引用分析,从创建最多的大对象里,右键点选进行GC Roots线程追踪、incoming reference分析,可以把大对象的根引用对象以及所在的线程方法名打印出来,基本可以判断大对象的出处和来源。

e0025ccf9d4978a95ea42e5c2bb4c43f.png

                     gcroot追踪和引用分析

3、通过MAT给出内存泄漏分析报告,查看诊断出来可能存在内存泄漏的线程栈和详细的大对象信息。

001a43cc30aba0ca4c7bd579f0e6bab6.png

                               内存分析报告

下面就總結一下常見的OOM類型和原因,業務系統中最常發生就是堆內存的溢出,它的排查方法就是要結合dump日誌+mat分析工具,分析大對象類型,線程堆棧,並結合業務場景來分析為何會發生內存洩漏的原因。

01b340ce2b8c9870c1d8f9fb50c11af2.png

                             OOM类型与原因

以上類型的OOM都已更新在我的github地址,每種類型除了給出了執行代碼,還需要同步修改IDE的jvm參數方能快速觸發OOM場景,下載地址在文章最末尾。

故障分析总结


綜合來看,故障分析篇,主要是三個核心觀點:

1、要在上線前的壓測環節將JVM參數調整至最優,後續就不在改動這一塊的參數。

2、如果上線以後還會發生了FullGC和OOM等情況,那麼有限需要去做架構和代碼層級的優化,這個時候分析GC日誌要比直接調優JVM參數要好。

3、JVM診斷和分析工具,利用好jstat或者VisualGC觀察GC次數和頻率,利用好jmap dump關鍵證據,然後使用MAT工具去!

推荐一个学习库:
https://github.com/StabilityMan/StabilityGuide
实例中涉及到的代码:
https://github.com/tisonkong/JavaAssemble/tree/master/jvm

ef54ac5d6daf5ee8341337b61a9c8077.png

1247ba33c46efbdd57e8ccd5d5a606fe.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值