前言
在工作中,我们有可能在sit开发环境或者生产环境遇到JVM OOM的问题或者某个JAVA进程的CPU使用率居高不下。这个时候就需要我们通过对JVM的分析找到问题点。一般要么是JVM启动参数设置不到位或者我们的代码中出现了bug(如果是代码中出现bug,这个难度就比较大了)。下面我们就分两个方面描述如何定位到问题,一个是Java进程CPU居高不下,另一个是OOM的问题
一、Java进程CPU使用率居高不下
如果CPU使用率居高不下,一般的情况是我们的代码中出现了死锁的现象。下面我们自己写一个线程死锁的问题,然后看下如何定位问题。
死锁代码示例
public class DeadLock {
private static Object object = new Object();
public static void getLock() {
synchronized (object) {
while (true) {
//获取到锁的线程在这里死循环不释放锁
}
}
}
public static void main(String[] args) {
new Thread(() -> {
getLock();
}).start();
new Thread(() -> {
getLock();
}).start();
new Thread(() -> {
getLock();
}).start();
System.out.println("---------end-------------------");
}
}
在linux对代码进行javac编译和java运行,发现cpu飙高
top命令下查看
查看线程死锁问题,一般我们使用jstack命令进行查找
先通过jps -lm找到我们需要找的java进程
[root@localhost ~]# jps -lm
41298 sun.tools.jps.Jps -lm
40296 DeadLock
所以我们需要对进程40296 进行进一步观察
jstack 40296
由下图可以知道是因为线程死锁了,因为两个线程状态为Blocked的线程出现了waiting on <0x00000000c5008ee0>
我们可以过滤下关键之wait直接找下是否是因为死锁,实际定位中,我们通过该命令会打出一大堆信息,让我们在linux屏幕上一个一个找,太慢,我经常把信息复制到windows的txt文档,然后ctl f 搜索"wait"一个一个查找定位。由上图我们可以知道在代码的第7行出现了死锁现象,所以我们需要到对应代码第7行附近看下是不是锁没有释放。
二、OOM问题
这个就比较麻烦了,我们得分两种情况来进行阐述如何进行定位问题。一个是我们自己的开发环境,一种是生产环境。我们需要说明的是,在生产环境上能够进行的JVM定位问题的方法在开发环境是适用的。但是开发环境上的方法是不一定适用生产环境的。理由是我们可以在开发环境上直接把内存dump下来进行分析,但是生产上需要慎重考虑了,除非你们环境做了高可用,这个机器挂了不影响业务正常运行。
我们先给出一个有问题的代码示例
我们写一个不断,往list加对象,导致OOM
import java.util.ArrayList;
import java.util.List;
public class OOM {
public static void main(String[] args) {
List<Object> list = new ArrayList<>();
while (true){
Object object = new Object();
list.add(object);
}
}
}
java -Xms10M -Xmx10M -XX:+PrintGC OOM
为了快速报错OOM。我设置堆大小为10M,在接下来的测试中我将堆设置为200M,这样我们才有时间进行定位排查
2.1 开发环境定位OOM
方式一:
使用jdk原生命令进行查看
# 该命令能够在线查看jvm中占内存前20个最多的对象是哪些
# 这个命令对机器服务性能是有一些影响,但是影响不大,所以可以在生产上直接使用
jmap -histo 63903 | head -20
通过观察可以看到Object对象一直在上涨
方式二:
将内存对象dump出来,然后在jconsole或者jvisual工具打开观察
# 该命令会把jvm在内存的对象都dump 出来,不要在生产上执行,因为一直jvm在内存中对象很大,这个是耗时操作,影响业务
jmap -dump:format=b,file=OOM 63903
然后将OOM这个文件下载到windows使用jdk自带的工具进行查看,这里举例使用jvisualvm进行查看
jvisualvm在你java_home的bin目录下
双击打开后。点击文件装入,选择堆dump,将刚刚的OOM文件导入
打开后就是这个界面,一般我会到下面的 “类标签”,看下大体哪些类的实例偏多,
通过观察,我们知道Object对象偏多,这个时候,就需要我们去代码中查找了,这就要求你对代码非常熟悉。定位问题还好,但是需要从代码找到bug,这个才是最难的。
当然我们可以打开OQL控制台,通过类似sql的语句进一步查询。如果需要了解,可以去网上找找看
2.2生产环境定位OOM
生产环境和开发环境不同,很多时候,我们不能直接将jvm内存中的java对象dump出来。这里有几种方式推荐
第一种方式
# 在java启动程序加入该参数,意味着当java程序发生OOM的时候,会在进程挂掉之前,把jvm当前的内存对象保存起来
-XX:+HeapDumpOnOutOfMemoryError
第二种方式
在程序发生OOM时候,让运维先别把服务重启,而是马上执行jmap -dump:format=b,file=xxx pid把内存对象导出来。一般这种方式不太推荐,因为一般生产需要马上恢复。但是如果可以dump的话,建议直接dump
第三种方式
将产生OOM的机器执行jmap -dump:format=b,file=xxx pid,但是前提是该服务做了高可用,我们挂了这个台机器不影响正整个系统使用
2.3 full gc 情况查看
通常运行命令如下:
jps -lm查看对应java进程pid
jstat -gc 6 3000
即会每5秒一次显示进程号为6的java进成的GC情况,
显示内容如下图: 显示的有点错位
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总耗时
总结
JVM问题定位主要分为CPU线程锁或者JVM的OOM问题。比较难的是OOM问题的定位,因为还需要区分是不是生产环境。并且即使问题定位出来,还要找到具体哪一行代码有问题,这个是最难的。需要我们对自己的业务代码非常熟悉不然很难定位。在实际工作中,我们有的同事问题是定位到了,但是找不到具体是哪一行代码,因为里面包含很多第三方类库,不知道哪个地方有问题。所以后面只能使用重启大法了。