技术干货实战(5)- JVM 性能监控工具之jstack、jstat等命令
作者:
修罗debug
版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
日常应用巡检期间,突然发现线上应用服务器CPU飙高、内存占用过高等情况时,传统的做法是不管三七二十一,直接重启线上应用,一了百了,这种方式虽然有时候可以解决一时的问题,但是却会让工程师错失排错、揪出问题根源的机会;本文将介绍出现上述等情况时,如何对整个应用服务器的整体性能、Java应用服务进行监控、排查以及定位,其中的jvm命令包括:jstack、jstat等等
在现实企业级Java应用开发、维护中,有时候我们会碰到下面这些问题:
(1)OOM,即OutOfMemoryError,内存溢出/内存不足;
(2)线程死锁 或者 锁争用(Lock Contention)
(3)Java进程消耗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为例,查看其线程中最耗费CPU的Java线程并定位堆栈信息,用到的命令有top、printf、jstack、grep等
已经确定了进程id为 13884 后,接下来查看该进程内部最耗费CPU的线程,命令为:
top -Hp 13884
如下图所示:
TIME那一列就是各个线程耗费的CPU时间,CPU时间最长的是线程ID为13893的线程,然后执行以下命令:
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)作用: jstat(JVM统计监测工具)可用于查看应用所占堆内存各个区的情况以及GC的情况,在实际应用期间也是很广泛的!
(2)语法格式:
jstat [ generalOption | outputOptions vmid [interval[s|ms] [count]] ]
vmid是Java虚拟机的ID,在Linux/Unix系统上一般就是进程ID,interval是采样时间间隔,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命令的由来),同时释放Eden和S1内存空间,那些存活的对象年龄 +1,YGC次数 +1;
随着系统的运行,会发现Eden空间再次不足,此时再次触发YGC,其过程也是一个道理,从Eden区和S0区标记出仍然存活的对象并将其Copy至S1区,释放空间,对象年龄+1,YGC+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,一个相信技术改变生活、技术成就梦想 的攻城狮;如果本文对你有帮助,请关注公众号,并动动手指收藏、点赞、以及转发哦!!!