现实企业级线上应用可能会发生内存泄露、线程死锁、Java进程消耗CPU过高等等情况,有的人遇到上述问题只是重启服务,而不会深究问题根源,导致此后问题会重复出现,所以理解并解决这些问题是Java高级开发必备要求,本文将介绍JVM性能监控工具,帮你解决这些问题
为什么java面试喜欢问垃圾回收以及内存模型这种问题?是属于面试八股文吗?
简历中你是否敢写
熟悉GC常用算法,熟悉常见垃圾回收器,具有实际JVM调优实战经验
?
常用命令
jps
jps(JVM Process Status Tool),它的功能和Linux中ps命令类似,可以列出正在运行的虚拟机进程
1 | jps 列出运行中的JVM进程 |
1 | jps -v 输出虚拟机进程启动时显式指定的JVM参数 |
jmap
jmap(Memory Map for Java)生成虚拟机的内存快照,当内存飙升时候可以使用jmap来查看
1 | jmap -histo:live <pid> | more |
打印堆的对象统计,包括对象数 、内存大小等。这个命令执行的话,JVM会先触发GC,然后再统计信息
参数 | 说明 |
---|---|
num | 序号 |
#instances | 实例数量 |
#bytes | 占用内存大小 |
class name | 类名称 |
1 | jmap -dump:live,format=b,file=xxx <pid> |
在当前目录下导出一个存储当前堆内存信息的dump文件(dump文件是进程的内存镜像),文件中存储的是当时那一时刻的堆的信息
这个命令执行,JVM会把整个heap信息写入到一个文件,heap如果比较大的话,就会导致这个过程非常耗时,并且执行过程中为了保证dump信息是可靠的,所以会暂停应用,线上系统一定要慎用!
导出成功后,用分析工具(比如jhat MAT)导入文件即可以分析
因为jmap很耗时,建议在java项目启动时添加非标参数
-XX:+HeapDumpOnOutOfMemoryError
这样当溢出时会自动将内存快照打印
jstack
jstack(Stack Trace for Java)显示虚拟机的线程快照。线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等都是导致线程长时间停顿的常见原因。线程出现停顿的时候通过 jstack 来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做些什么事情,或者等待着什么资源
1 | jstack <pid> |
参数 | 说明 |
---|---|
http-nio-8088-Acceptor-0 | 线程名 |
prio | java优先级 |
os_prio | 操作系统优先级 |
tid | java线程的id, threadId |
nid | 系统内核里面的线程唯一id |
使用实例:查找线上Java进程导致CPU占用过高的问题
- 使用一段死循环模拟CPU高占用
通过
top
命令查看当前cpu占用率最高的进程通过
top -H -p 9522
命令查看某个pid进程下各线程的cpu占用情况
可以看到线程id=9548cpu占用率最高,因为线程id在jstack中是以十六进制显示的,我们将9548转成十六进制是:0x254c
- 使用
jstack 9522 | grep -10 '0x254c'
找到进程的调用堆栈情况,并通过grep
按照线程id搜索,返回匹配结果前后10
行的数据
- 结果:图中可以看到
JvmMonitorApplication
类的第18行导致的问题,分析正确
通过Arthas可以更快速的定位,后面给出方法
jinfo
jinfo(Configuration Info for Java)的作用是实时查看和调整虚拟机各项参数。使用jps
命令的-v
参数可以查看虚拟机启动时显式指定的参数列表,但如果想知道未被显式指定的参数的系统默认值,就可以使用info
进行查询
1 | jinfo <pid> 查看所有参数 |
jstat
jstat(JVM Statistics Monitoring Tool)是用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据,在没有GUI图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具
- class 用于查看类加载情况的统计
- compiler 用于查看HotSpot中即时编译器编译情况的统计
- gc 用于查看JVM中堆的垃圾收集情况的统计
- gccapacity 用于查看新生代、老生代及持久代的存储容量情况
- gcmetacapacity 显示metaspace的大小
- gcnew 用于查看新生代垃圾收集的情况
- gcnewcapacity 用于查看新生代存储容量的情况
- gcold 用于查看老生代及持久代垃圾收集的情况
- gcoldcapacity 用于查看老生代的容量
- gcutil 显示垃圾收集信息
- gccause 显示垃圾回收的相关信息(通-gcutil),同时显示最后一次仅当前正在发生的垃圾收集的原因
- printcompilation 输出JIT编译的方法信息
例子: 查看垃圾回收统计
1 | jstat -gc <pid> 1000 10 |
参数 | 说明 |
---|---|
-gc | 打印方式 |
进程id | |
1000 | 每1000ms打印一次 |
10 | 打印10次 |
参数 | 说明 |
---|---|
S0C | 第一个幸存区的大小 |
S1C | 第二个幸存区的大小 |
S0U | 第一个幸存区的使用大小 |
S1U | 第二个幸存区的使用大小 |
EC | 伊甸园区的大小 |
EU | 伊甸园的使用大小 |
OC | 老年代的大小 |
OU | 老年代的使用大小 |
MC | 方法区大小 |
MU | 方法区的使用大小 |
CCSC | 压缩类空间大小 |
CCSU | 压缩类空间的使用大小 |
YGC | 年轻代垃圾回收次数 |
YGCT | 年轻代垃圾回收消耗的时间 |
FGC | 老年代回收次数 |
FGCT | 老年代垃圾回收消耗的时间 |
CGC | 并发回收次数 |
CGCT | 并发回收消耗的时间 |
GCT | 垃圾回收消耗总时间 |
使用实例:写一个需要频繁GC的程序,用
jstat
观察GC情况
- 代码
- 观察状态信息
分析图中结果可以发现YGC(年轻代垃圾回收次数)增加的非常快,而FGC(老年代回收次数)不会执行
Arthas
简介
Arthas
是Alibaba开源的Java诊断工具,深受开发者喜爱。
安装方式极为简单,按照首页引导即可,下面介绍简单使用
dashboard
当前系统的实时数据面板
thread
查看当前线程信息,查看线程的堆栈
实例1: 前文使用jstack查找线上Java进程导致CPU占用过高的问题,使用arthas会更加简单
- dashboard查询占用cpu较高线程
1 | dashboard |
发现线程id为20(注意这个ID不能跟jstack中的nativeID一一对应)的线程占用过高
- 查看线程堆栈
1 | thread 20 |
图中分析正确
实例2: 查询java进程中的所有死锁信息
- 代码模拟死锁
- 启动Arthas,执行
thread -b
查看死锁位置
更多Arthas用法,去官网查询
同样使用jstack
同样可以查找死锁
1 | jstack <pid> | grep -10 'deadlock' |
可以看到图中Thread-1
想要的锁被Thread-2
持有;Thread-2
想要的锁被Thread-1
持有