本文将介绍如何使用一些常用的调优工具来实现JVM调优。
下面废话不多说,直接开干
一、准备
为了具备jvm调优的场景,这里准备如下图这样一段简单的代码。
public class FullGCProblem {
//线程池
private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(50,
new ThreadPoolExecutor.DiscardOldestPolicy());
public static void main(String[] args) throws Exception {
//50个线程
executor.setMaximumPoolSize(50);
while (true){
calc();
Thread.sleep(100);
}
}
//多线程执行任务计算
private static void calc(){
List<UserInfo> taskList = getAllCardInfo();
taskList.forEach(userInfo -> {
executor.scheduleWithFixedDelay(() -> {
userInfo.user();
}, 2, 3, TimeUnit.SECONDS);
});
}
//模拟从数据库读取数据,返回
private static List<UserInfo> getAllCardInfo(){
List<UserInfo> taskList = new ArrayList<>();
for (int i = 0; i < 100; i++) {
UserInfo userInfo = new UserInfo();
taskList.add(userInfo);
}
return taskList;
}
private static class UserInfo {
String name = "zhangsan";
int age = 18;
BigDecimal money = new BigDecimal(999999.99);
public void user() {
//
}
}
}
在工程的资源目录下,做如下配置
使用maven打成jar包,放到linux环境下运行起来。
启动命令如下:
java -cp JVM_Demo-v1.0.jar -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -Xms200M -Xmx200M com.hl.ex13.FullGCProblem |
二、CPU 占用过高排查
1、先通过 top 命令找到消耗 cpu 很高的进程
top 命令是我们在 Linux 下最常用的命令之一, 它可以实时显示正在执行进程的 CPU 使用率、 内存使用率以及系统负载等信息。 其中上半部分显示的是系统的统计信息, 下半部分显示的是进程的使用率统计信息。
由上图中,可以看出,进程id为7139的进程cpu使用率偏高。
2、执行命令“ top -p 7139 -H”, 单独监视进程7139下的所有的线程信息,如下图
从这个图中,可以看出,线程id为7141和7142两个线程占用cpu较高。
3、执行命令 “jstack 7139> jstack.txt” 命令,将线程的所有的信息做dump记录。完成后,将jstack.txt文件下载到本地,使用编辑器打开。
4、将2步中得到的线程id 7141使用计算器,转换成16进制。得到是0x1be5。
5、拿着这个转换后的数,去刚下载到本地的文本中去查找,如下图所示
从这个图,就可以发现找是 VM 的线程占用过高。
6、这里使用jstat命令监控gc的情况
命令如下:
jstat -gc 7139 5000 20 | awk '{print $13, $14,$15,$16,$17}' |
监视效果如下:
从这个图中发现,YGC次数很少,而FGC就很多。也就是说JVM在拼命的做FULLGC。
7、接下来,还可以使用jmap命令,查看堆内存的情况
命令如下:
jmap -histo 7139 | head -20 |
监控如下图:
从这个监控结果中,发现了堆中有很多这些的实例。
由此不难得出,问题就出在我们的代码里了
代码中不停地往线程池里面的放入对象。而从这个源码里面看出,最多线程数是线程的最大值,相当于是没有上限,而关键的是使用DelayedQueue队列,但它是一个无界队列。那就会出现一些来不及执行的任务所持有的对象在这个队列占位,这样一来这些对象也就没有办法回收掉。但是我们jar启动的时候,指定堆的最大值是200M,因此也就不难理解为什么会出现频繁的FULLGC了,因为对象持续得不到回收就进入了老年代,而老年代也是有大小限制的。
三、总结
在 JVM 出现性能问题的时候。 (表现上是 CPU100%, 内存一直占用)
1、 如果 CPU 的 100%, 要从两个角度出发, 一个有可能是业务线程疯狂运行, 比如说想很多死循环。 另一种可能性, 就是 GC 线程在疯狂的回收, 因为 JVM 中垃圾回收器主流也是多线程的, 所以很容易导致 CPU 的 100%
2、 在遇到内存溢出的问题的时候, 一般情况下我们要查看系统中哪些对象占用得比较多, 这里采用一个很简单的代码, 在实际的业务代码中, 找到对应的对象, 分析对应的类, 找到为什么这些对象不能回收的原因。涉及到的知识包括可达性分析算法, JVM 的内存区域, 还有垃圾回收器等。