33-一条SQL引发的系统卡死(下)-解决方案

268 阅读6分钟

我们接着上篇文章的案例继续进行分析与优化,首先回顾下上文案例对应的问题结果:图片

每隔20秒会让300多MB的Eden区满触发一次Young GC,一次Young GC耗时50毫秒左右。

每隔30分钟会让老年代里600多MB空间占满,进而触发一次CMS的GC,一次Full GC耗时300毫秒左右。

分析到这里,说句实话,仅仅根据可视化监控和推论是绝对没法往下分析了,因为我们并不知道老年代里到底为什么会有那么多的对象,因此我们需要进一步探查到底为什么有这么多的对象进入老年代!

老年代对象探秘

当时通过jstat工具进行过三天的连续监控,其实我们发现每次Yong GC后进入老年代的对象很少,每次Yong GC后剩余的存活对象也就几十MB是完全能够存入Survivor区的,但是由于动态年龄判断规则,Survivor区只有70MB,那么也会偶尔进入老年代的几十MB对象,但这也不至于让老年代的Full GC发生这么频繁。如下图所示:

图片

那么到底是什么原因导致我们的老年代30分钟就能存满,并且触发Full CG的呢?

这时我们再进一步从头开始观察数据发现:当系统每运行一段时间后,突然有5,600MB数据一下进入老年代!

加上我们刚才分析的,每隔一段时间就有几十MB对象会进入到老年代,那么刚好达到老年代阈值68%,从而触发Full GC!

每次回收后,后续继续每隔一段时间进入5,600MB对象,这也就导致了每隔差不多半小时就要执行一次Full GC!这就是系统的根本原因所在!

通过以上分析,系统突然进入5,600MB对象到老年代,只有一个原因:那就是 大对象!

这种大对象是不会直接进入Eden区域的,而是直接进入老年代,所以现在的情况如下图所示:

图片

定位大对象

分析到这儿,我们只需要定位到到底是什么对象突然的进入所导致的,从而定位到我们的代码问题。

那么大家可以结合我们之前讲解的jstat工具进行打印观察,当发现系统突然增大了几百M对象进入老年代,即可通过jmap工具导出一份dump内存快照,然后通过jhat或者是Visual VM可视化工具进行分析。

jhat的使用之前已经介绍过,VisualVm的使用我们将贴在最下方进行简单介绍。

最后通过内存快照的分析,发现几百M大对象就是几个Map的数据结构,最后定位到代码中发现居然是从数据库查询出来并进行封装的。

接着开始逐步排查对应的所有SQl语句,结果最后发现确实是有一条SQL有问题,在特定的条件下会触发该sql的执行导致对应的系统卡顿。

这条SQL很简单就是:

select * from table;

没错,就是没有带任何where条件的查询语句,直接将数据库中几十万条数据全部查询出来,导致每隔一段时间直接想内存中分配几个上百MB大对象,最后进入老年代!

案例优化

  1. 让对应负责SQL语句的开发进行bug修复,SQL的无条件查询要谨慎,不允许直接查询表中所有数据,避免在特定情况下触发导致问题
  2. 年轻代明显过小,Survior区域空间太小,70MB很容易触发动态年龄判断,让对象进入老年代

因此最后优化的JVM参数如下:

-Xms1536M -Xmx1536M -Xmn1024M -Xss256K 
-XX:SurvivorRatio=5 -XX:PermSize=256M
-XX:MaxPermSize=256M  -XX:+UseParNewGC 
-XX:+UseConcMarkSweepGC 
-XX:CMSInitiatingOccupancyFraction=92
-XX:+CMSParallelRemarkEnabled 
-XX:+UseCMSInitiatingOccupancyOnly
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintHeapAtGC

在不改变机器配置的情况下,我们将新生代内存由512M增大到700MB,每个Survior就是150M左右,这样Yong GC每次剩余存活的对象几十M也一般不会进入老年代了。

另外将参数“-XX:CMSInitiatingOccupancyFraction=92” 调整为了92,避免老年代过早就触发GC。

通过以上步骤的优化后最终该系统线上运行基本上 每分钟一次Yong GC,一次几十毫秒,Full GC几乎很少,几乎在10天才会发生一次,一次几百毫秒,频率很低!

以下为Visual VM工具的介绍和使用

Visual VM 介绍

VisualVM( All-in-One Java Troubleshooting Tool) 是功能最强大的运行监视和故障处理程序之一, 曾经在很长一段时间内是Oracle官方主力发展的虚拟机故障处理工具。

Oracle曾在VisualVM的软件说明中写上了“All-in-One”的字样, 预示着它除了常规的运行监视、 故障处理外, 还将提供其他方面的能力, 譬如性能分析( Profiling) 。VisualVM的性能分析功能比起JProfiler、 YourKit等专业且收费的 Profiling工具都不遑多让。

VisualVM 是一款免费的,集成了多个 JDK 命令行工具的可视化工具,它能为您提供强大的分析能力,对 Java 应用程序做性能分析和调优。这些功能包括生成和分析海量数据、跟踪内存泄漏、监控垃圾回收器、执行内存和 CPU 分析,同时它还支持在 MBeans 上进行浏览和操作。

Visual VM的作用

VisualVM基于NetBeans平台开发工具, 所以一开始它就具备了通过插件扩展功能的能力, 有了插件扩展支持, VisualVM可以做到:

  • 显示虚拟机进程以及进程的配置、 环境信息( jps、 jinfo) 。
  • 监视应用程序的处理器、 垃圾收集、 堆、 方法区以及线程的信息( jstat、 jstack) 。
  • dump以及分析堆转储快照( jmap、 jhat) 。
  • 方法级的程序运行性能分析, 找出被调用最多、 运行时间最长的方法。
  • 离线程序快照:收集程序的运行时配置、 线程dump、 内存dump等信息建立一个快照, 可以将快照发送开发者处进行Bug反馈。
  • 其他插件带来的无限可能性。

IDEA 插件安装

图片

当安装后IDEA上方出现这两个按钮后即代表安装成功:

图片

将代码以Visual VM的方式进行启动,就会弹出对应的窗口进行展示:

图片

Visual VM的使用

通过监控页面可以监控CPU、内存、类、线程的运行情况,如下图:

图片

dump文件分析和查看

图片

图片OpenCoder技术交流群创建了!图片

为了方便大家在阅读学习专题文章的同时有问题及时交流探讨,我们特此创建了此群,此群的目的是为了给大家营造一个学习氛围以及相互交流探讨的平台;群里也有BATJ等大厂的大佬们坐镇,不定期进行面经分享,技术专题分享,最新行业现状,免费视频资料等