你们项目如何排查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进程
,并显示虚拟机执行主类名称以及进程idC:\>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
点赞,靓仔!!!