你们项目如何排查JVM问题

对于还在正常运⾏的系统:

  • 通过各个命令的结果,或者jvisualvm等⼯具来进⾏分析
  • 可以使⽤jmap来查看JVM中各个区域的使⽤情况
  • 可以通过jstack来查看线程的运⾏情况,⽐如哪些线程阻塞、是否出现了死锁
  • 可以通过jstat命令来查看垃圾回收的情况,特别是fullgc,如果发现fullgc⽐较频繁,那么就得进⾏调优了
  • ⾸先,初步猜测频繁fullgc(老年代整体大面积垃圾回收)的原因
  • 如果频繁发⽣fullgc但是⼜⼀直没有出现内存溢出,系统运行的好好的
  • 那么表示gc实际上是一下可以回收很多对象了,所以这些对象最好能在younggc(新生代垃圾回收)过程中就直接回收掉,避免这些对象进⼊到⽼年代,甚至搞到永久区里去
  • 对于这种情况,就要看看什么原因了
  • 考虑这些存活时间不⻓的对象是不是⽐较⼤,导致年轻代放不下,直接进⼊到了⽼年代,尝试加⼤年轻代的⼤小
  • 检查一下是哪个线程使用内存太多了
  • 检查一下是哪个线程占⽤CPU太多了,定位到具体的⽅法,优化这个⽅法的执⾏,看是否能避免某些对象的创建,从⽽节省内存

对于已经发⽣了OOM(内存溢出)的系统:

  1. ⼀般⽣产系统中都会设置当系统发⽣了OOM时,⽣成当时的dump⽂件
  • - XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/base
  1. 我们可以利⽤jsisualvm(可视化)等⼯具来分析dump⽂件
  2. 根据dump⽂件找到异常的实例对象,和异常的线程(占⽤CPU⾼),定位到具体的代码
  3. 然后再进⾏详细的分析和调试

JVM图形化查看详细

  • 图形界面可以去jdk的bin下面找到jvisualvm.exe -> 这是一个自带的图形化界面
  • 有一些好用的插件可以试着安装Visual GC btrace

查看线程情况

java jvm排查示例_后端


查看各个线程使用的内存和cpu的情况

java jvm排查示例_开发语言_02


查看垃圾回收情况

java jvm排查示例_后端_03

查看dump跟踪信息

java jvm排查示例_后端_04

JVM命令查看详细

jps -> Java ps:查看正在运行的Java进程

  • jps: 可以列出正在运行的Java进程,并显示虚拟机执行主类名称以及进程id
C:\>jps
5932 测试Collection
  • 常见的选项: jps -ljps -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 进程idjps -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

点赞,靓仔!!!