你们项目如何排查JVM问题
对于还在正常运⾏的系统:
- 通过各个命令的结果,或者jvisualvm等⼯具来进⾏分析
- 可以使⽤jmap来查看JVM中各个区域的使⽤情况
- 可以通过jstack来查看线程的运⾏情况,⽐如哪些线程阻塞、是否出现了死锁
- 可以通过jstat命令来查看垃圾回收的情况,特别是fullgc,如果发现fullgc⽐较频繁,那么就得进⾏调优了
- ⾸先,初步猜测频繁
fullgc(老年代整体大面积垃圾回收)
的原因 - 如果频繁发⽣fullgc但是⼜⼀直
没有出现内存溢出
,系统运行的好好的 - 那么表示gc实际上是
一下可以回收很多对象了
,所以这些对象最好能在younggc(新生代垃圾回收)
过程中就直接回收掉,避免这些对象进⼊到⽼年代,甚至搞到永久区里去 - 对于这种情况,就要看看什么原因了
- 考虑这些
存活时间不⻓的对象是不是⽐较⼤
,导致年轻代放不下,直接进⼊到了⽼年代,尝试加⼤年轻代的⼤小 - 检查一下是哪个线程使用内存太多了
- 检查一下是哪个线程占⽤CPU太多了,定位到具体的⽅法,优化这个⽅法的执⾏,看是否能避免某些对象的创建,从⽽节省内存
对于已经发⽣了
OOM(内存溢出)
的系统:
- ⼀般⽣产系统中都会设置当系统发⽣了OOM时,⽣成当时的dump⽂件
- XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/base
- 我们可以利⽤jsisualvm(可视化)等⼯具来分析dump⽂件
- 根据dump⽂件找到异常的实例对象,和异常的线程(占⽤CPU⾼),定位到具体的代码
- 然后再进⾏详细的分析和调试
JVM图形化查看详细
- 图形界面可以去jdk的bin下面找到
jvisualvm.exe
-> 这是一个自带的图形化界面- 有一些好用的插件可以试着安装
Visual GC
btrace
查看线程情况
查看各个线程使用的内存和cpu的情况
查看垃圾回收情况
查看dump跟踪信息
JVM命令查看详细
jps
-> Java ps:查看正在运行的Java进程
- jps: 可以列出正在运行的
Java进程
,并显示虚拟机执行主类名称以及进程id
C:\>jps
5932 测试Collection
- 常见的选项:
jps -l
jps -v
jps -l
-> 输出主类全类名,如果进程执行的是Jar包,输出jar包名字
C:\>jps -l
5932 rod.集合.测试Collection
jps -v
-> 程序启动时指定的jvm参数
5932 测试Collection
-agentlib:jdwp=transport=dt_socket,
address=127.0.0.1:54731,
suspend=y,
server=n
-javaagent:C:\Users\欧皇小德子\AppData\Local\JetBrains\IntelliJIdea2021.2\captureAgent\debugger-agent.jar
-Dfile.encoding=UTF-8
jstack
-> Java stack:打印线程快照
- 查看某个Java进程中
所有线程的状态
。 - 一般用来
定位线程出现长时间停顿的原因
,如发生死循环,死锁,请求外部资源长时间等待等! - 常见的选项:
jstack 进程id
jps -v
jstack 进程id
-> 进程中所有线程的状态,只要程序还在走,就打印轨迹
[C:\~]$ jstack 37476
2022-02-22 19:17:04
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.231-b11 mixed mode):
"DestroyJavaVM" #24 prio=5 os_prio=0 tid=0x0000000002f94000 nid=0x8a40 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"myThreadB" #23 prio=5 os_prio=0 tid=0x0000000025314000 nid=0x5018 waiting for monitor entry [0x00000000266bf000]
java.lang.Thread.State: BLOCKED (on object monitor)
at rod.TestMain.lambda$main$1(TestMain.java:35)
- waiting to lock <0x0000000743a6a820> (a java.lang.Object)
- locked <0x0000000743a6a830> (a java.lang.Object)
at rod.TestMain$$Lambda$2/1349414238.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"myThreadA" #22 prio=5 os_prio=0 tid=0x0000000025313000 nid=0x2794 waiting for monitor entry [0x00000000265bf000]
java.lang.Thread.State: BLOCKED (on object monitor)
at rod.TestMain.lambda$main$0(TestMain.java:21)
- waiting to lock <0x0000000743a6a830> (a java.lang.Object)
- locked <0x0000000743a6a820> (a java.lang.Object)
at rod.TestMain$$Lambda$1/1873653341.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"Service Thread" #21 daemon prio=9 os_prio=0 tid=0x0000000024ff1000 nid=0x8cc4 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
...
"VM Thread" os_prio=2 tid=0x00000000215f7000 nid=0x8710 runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000002faa800 nid=0x9378 runnable
...
发现一个Java级死锁:
=============================
"myThreadB":
等待锁定监视器 0x0000000021601c08 (object 0x0000000743a6a820, a java.lang.Object),
这是由 "myThreadA"
"myThreadA":
等待锁定监视器 0x0000000021604338 (object 0x0000000743a6a830, a java.lang.Object),
这是由 "myThreadB"
列出的线程的Java堆栈信息:
===================================================
"myThreadB":
at rod.TestMain.lambda$main$1(TestMain.java:35)
- waiting to lock <0x0000000743a6a820> (a java.lang.Object)
- locked <0x0000000743a6a830> (a java.lang.Object)
at rod.TestMain$$Lambda$2/1349414238.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"myThreadA":
at rod.TestMain.lambda$main$0(TestMain.java:21)
- waiting to lock <0x0000000743a6a830> (a java.lang.Object)
- locked <0x0000000743a6a820> (a java.lang.Object)
at rod.TestMain$$Lambda$1/1873653341.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
- 可能看到一些线程的轨迹:
-
DestroyJavaVM
-> 销毁线程**(RUNNABLE=运行状态)** -
myThreadB丶myThreadA
-> 我们直接定义的线程**(BLOCKED=阻塞状态)** -
Service Thread
-> 还有很多名称的daemon
守护线程也是运行状态
就不列举了 -
GC
-> 垃圾回收线程**(RUNNABLE=运行状态)**
- 并且在最后提示出了死锁发生的位置
jmap
-> Java map:导出堆内存映像文件
- jmap主要用来用来导出
堆内存映像文件,看是否发生内存泄露
等。
- 内存溢出: 内存满了,炸了,你还要创建新对象,直接爆炸 ->
溢出比较好记,就是满了,反过来就是泄露
- 内存泄露: 内存没满好好的,但是
有很多垃圾需要可以回收却回收不了
,站着茅坑不拉屎
生产环境一般会配置如下参数,让虚拟机在OOM异常出现之后自动生成dump文件
- Dump文件是进程的内存镜像。可以把程序的执行状态通过调试器保存到dump文件中。
- 主要是用来在系统中出现异常或者崩溃的时候来生成dump文件
- 然后用调试器进行调试,这样就可以把生产环境中的dump文件拷贝到自己的开发机上
- 调试就可以找到程序出错的位置。
//输出错误堆Dump信息 路径/Users/peng
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/Users/peng
- 执行如下命令即可
手动获得dump文件
jmap -dump:file=文件名.dump 进程id
jstat
-> java stat: 查看jvm统计信息
- jstat可以显示
本地或者远程虚拟机
进程中的类装载、 内存、 垃圾收集、 JIT(编译器)编译
等运行数据 - 用jstat查看一下类装载的信息。我个人很少使用这个命令,
命令行看垃圾收集信息真不如看图形界面方便
[C:\~]$ jstat -class 37476
加载类的个数 加载类的字节数 卸载类的个数 卸载类的字节数 花费的时间
Loaded Bytes Unloaded Bytes Time
661 1285.3 0 0.0 0.17
点赞,靓仔!!!