技术干货实战(5)- JVM 性能监控工具之jstack、jstat等命令

作者: 修罗debug
版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。


日常应用巡检期间,突然发现线上应用服务器CPU飙高、内存占用过高等情况时,传统的做法是不管三七二十一,直接重启线上应用,一了百了,这种方式虽然有时候可以解决一时的问题,但是却会让工程师错失排错、揪出问题根源的机会;本文将介绍出现上述等情况时,如何对整个应用服务器的整体性能、Java应用服务进行监控、排查以及定位,其中的jvm命令包括:jstackjstat等等


在现实企业级Java应用开发、维护中,有时候我们会碰到下面这些问题:

1OOM,即OutOfMemoryError,内存溢出/内存不足;

2)线程死锁 或者  锁争用(Lock Contention

3Java进程消耗CPU过高,打开云服务器的性能指标监控直方图时会发现CPU一直居高不下 ......


这些问题在日常开发、维护中可能被很多人忽视(比如有的人遇到上面的问题只是暴力重启或者调大内存,而不会深究其问题根源),但说实在的,倘若能够理解并解决这些问题,那将大大增强Java开发者的进阶技能;

本文将以debug新发布的新课 “Java核心技术-典型案例与面试实战系列二(SpringBoot2.0+企业真实案例)”中提及的、集群部署下的“权限管理平台”项目为案例,部署到Linux ECS Centos7上,并采用JVM相应的命令进行监控查看,访问地址为:http://history.huicairj.com/

一、jstack

1)作用:查看一个java进程内 线程占用的堆栈内存信息

2)语法格式:   

jstack [option]  pid
jstack [option] executable core
jstack [option] [server-id@]remote-hostname-or-ip

参数额外说明:

-l long listings,会打印出额外的锁信息,在发生死锁时可以用jstack -l pid来观察锁持有情况-m mixed mode,不仅会输出Java堆栈信息,还会输出C/C++堆栈信息(比如Native方法),如下图所示为 进程pid 13884 对应的项目 “权限管理平台”内各个线程持有锁的情况,输入命令后回车,并移动到输出的信息的最下面即可得到::



目前没有看到某个线程独占锁的情况,即None,也没有出现某个线程正在等待获取某个资源Waiting No Object;

一般如果出现死锁的话,会伴随着两个现象:A.某个线程等待获取某个资源的锁,即Waiting to lock monitor…   B.这个资源却被另外的线程所持有,即which is held by “Thread-xxxx”,如下图所示为多年前debug曾经的一次日常线上应用巡检时出现的情况:


而很明显两个线程在互相争夺对方的持有的资源,僵持不下,因此也就出现了:死锁,即dead lock

顺提一下,检测死锁还可以利用JVM自带的jconsole工具进行查看(在成功安装jdk时有个bin目录,里面的jconsole.exe就是),如下图所示:



3)命令实操:言归正传,jstack可以定位到线程堆栈,根据堆栈信息我们可以定位到具体的代码,所以它在JVM性能调优与监控中使用得非常多;

 

下面以“权限管理平台”对应的进程:13884为例,查看其线程中最耗费CPUJava线程并定位堆栈信息,用到的命令有topprintfjstackgrep

已经确定了进程id 13884 后,接下来查看该进程内部最耗费CPU的线程,命令为:   

top -Hp 13884

如下图所示:



TIME那一列就是各个线程耗费的CPU时间,CPU时间最长的是线程ID13893的线程,然后执行以下命令:   

printf "%x" 13893


得到13893的十六进制值为3645,在下面会用到;紧接着轮到jstack上场了,用于输出进程13893的堆栈信息,然后根据线程ID的十六进制值grep,如下:

jstack 13884 | grep 3654


回车后出现如下的结果:

"C2 CompilerThread0" #48 daemon prio=0 os_prio=0 tid=0x00007f45f0b80000 nid=0x188 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE


如果是出现这种结果 且 线上CPU真的飙高的话,那应该考虑关闭下C2编译器,即只需要在java -jar xx.jar 启动命令中,加下jvm的参数即可:-XX:-TieredCompilation –server ,其深层次的含义为:关闭JIT分层编译

这玩意比较深奥,感兴趣的小伙伴可以网上搜罗一番资料自行学习学习;

另外,有些时候执行 jstack 13884 | grep 3654 命令得到的结果可能是某个 类 在等待执行某个方法,那么这个时候确实代码写得有问题了,可以根据返回的结果提示找到具体的类方法代码即可!  


二、jstat

1)作用: jstatJVM统计监测工具)可用于查看应用所占堆内存各个区的情况以及GC的情况,在实际应用期间也是很广泛的!

2)语法格式:

jstat [ generalOption | outputOptions vmid [interval[s|ms] [count]] ]


vmidJava虚拟机的ID,在Linux/Unix系统上一般就是进程IDinterval是采样时间间隔,count是采样数目

在命令实操之前,首先得给大家介绍下JVM的内存结构,因为待会儿在解释 执行 jstat 命令后打印出来的结果时需要用到;如下图所示Java虚拟机给一个应用分配的运行时的内存结构,如下图所示:


各控制参数的含义如下所示:   

-Xms设置堆的初始(最小)空间大小。

-Xmx设置堆的最大空间大小。

-XX:NewSize设置新生代初始(最小)空间大小。

-XX:MaxNewSize设置新生代最大空间大小。

-XX:PermSize设置永久代(方法区)最小空间大小。

-XX:MaxPermSize设置永久代(方法区)最大空间大小。

-Xss设置每个线程的堆栈大小。

(注:方法区在JDK1.7的时候叫做永久代,到JDK1.8之后废弃了永久代改为元空间(meta space))

而堆内存head space那一块是我们最为关心的,因为几乎所有的对象实例、数组都存放在java堆里,它也是GC回收重点关注的地方,其内存结构我们单独抽出来,如下图所示:


 

不难看出:

堆内存 = 年轻代 + 年老代 + 永久代  (这个有点争议)
年轻代 = Eden区(Eden) + 两个Survivor区(From和To),即S0 和 S1

默认Eden:from :to = 8:1:1

3)命令实操:如下图输出的GC信息中,采样时间间隔为2s,共采样60次的结果:


 

上图输出的信息中各列的含义如下所示(以下的 容量 字段信息 单位为:字节 ):   

S0C、S1C、S0U、S1U:Survivor 0/1区分配的容量(Capacity)和使用量(Used)

EC、EU:Eden区分配的容量和使用量

OC、OU:年老代容量和使用量

PC、PU:永久代容量和使用量

YGC、YGT:年轻代GC次数和GC耗时

FGC、FGCT:Full GC次数和Full GC耗时

GCT:GC总耗时


在上图中我们可以看到,每隔2s采样一次,共采样次数为60次,在此期间发生了3次的YGC (因为debug的这台测试服务器本身内存不高:2g 而已),其发生的过程在上图中已经都标注出来了,诸位可以自行看看,下面再简单地总结下GC的相关概念以及触发GC的时机和过程:

1)基本概念

A. YGC :对新生代堆内存进行垃圾回收,即GC,频率比较高,因为大部分对象的存活寿命较短,在新生代里被回收,性能耗费较小。

B. FGC :全范围堆内存的GC,默认堆空间使用到达80%(可调整)的时候会触发FGC,以我们生产环境为例,一般比较少会触发FGC,有时30天或几个月会有一次;


2)触发GC的时机以及GC过程

A.YGC的时机Eden空间不足

像上图所展示的,Eden空间已经不足以支撑下次的对象内存分配了,因此会触发YGC,其GC过程比较简单:标记Eden区和其中一个S区,如S1仍然存活的对象,然后Copy将其搬迁至另一个S区,如S0 From To命令的由来),同时释放EdenS1内存空间,那些存活的对象年龄 +1YGC次数 +1

随着系统的运行,会发现Eden空间再次不足,此时再次触发YGC,其过程也是一个道理,从Eden区和S0区标记出仍然存活的对象并将其CopyS1区,释放空间,对象年龄+1YGC+1,如此循环反复,当对象年龄达到15时(默认是15,可以调整设置),即会开始将那些仍然存活的对象搬迁拷贝至年老代Old(以上过程是以标记-清除复制算法为例)


B.FGC的时机Old空间不足;Perm空间不足;显示调用System.gc() ,包括RMI等的定时触发;YGC时的悲观策略;dump live的内存信息时(jmap dump:live)


而对于FGC触发的时机则比较多,但常见无非是:Old空间不足 或 Perm空间不足,其GC过程比较复杂(取决于采用什么GC算法 和 垃圾回收器,经典的当然是:分代收集算法),在这里debug就不做过多介绍了,感兴趣的小伙伴可以网上搜罗一番,参考链接:https://www.cnblogs.com/bigbaby/p/12348968.html


总结:

1)代码下载:文中涉及到的“权限管理平台”项目源码数据库 可以通过关注微信公众号:程序员实战基地(扫描网站底部的微信公众号即可),回复数字: 101 ,即可下载 !

我是debug,一个相信技术改变生活、技术成就梦想 的攻城狮;如果本文对你有帮助,请关注公众号,并动动手指收藏、点赞、以及转发哦!!!