JVM 内存溢出排查

说明:记录一次JVM内存溢出的排查过程;

场景

项目开发完成后,首次提交到测试环境。测试、产品同事反馈页面先是操作响应慢,抛出超时异常,最后直接无法使用。查看日志后得知是内存溢出

重启服务后,我对前端主要的几个长RT接口,使用Arthas进行了trace

排查

Step1:SQL问题

锁定到了时间主要消耗在一次数据库操作,首先想到这段SQL是否有问题。

但我把SQL单独取出来,拼接参数执行,时长非常短,不应该啊。

Step2:拦截器

这期间,我做了很多尝试,看了很多JVM内存溢出的文章,但是基本没有帮助。于是,我换了一种思路,项目重启后,疯狂调用慢接口,然后使用Arthas的dashboard命令,查看是否有异常的进程,结果如下:

可以看到有进程被阻塞了,再使用thread id命令,查看该进程情况,如下:

从上往下看,找到自己项目中的代码,按图索骥,我找到项目中的下面这段代码。这段代码在拦截器类里,拦截器是对操作数据库拼接参数、封装结果集时进行拦截操作,该段代码是封装结果集时会执行的。

java 复制代码
	Cipher cipher = Cipher.getInstance(algorithmName, getSingleInstance());

问题可能出现在操作数据库,拦截器操作返回结果集的这一步,我先对该方法进行追踪,如下:

很明显,问题出在这里,每条记录耗时大几百毫秒,十条记录就是大几秒;

Step3:单例对象

java 复制代码
	Cipher cipher = Cipher.getInstance(algorithmName, getSingleInstance());

回过头来,再分析这行代码。该方法是jdk提供的,应该没啥问题,重点是参数列表中的第二个方法getSingleInstance(),该方法写在该类里,是成员方法,目的是创建单例对象作为参数。查看该成员方法,代码如下(已脱敏)

java 复制代码
	private static synchronized SingleBean getSingleInstance() {
	    SingleBean single = null;
	    if(single == null){
	        single = new SingleBean();
	    }
	    return single;
	}

该代码看上去怪怪的,单例对象不应该这么创建。于是我将代码修改成如下,即单例创建模式之一的双重检查锁定:

java 复制代码
	private static volatile SingleBean single = null;
	
	private ThisClass() {
	}
	
	/**
	 * 创建单例对象
	 */
	private static SingleBean getSingleInstance() {
	    if(single == null){
	        synchronized (ThisClass.class) {
	            if(single == null){
	                single = new SingleBean();
	            }
	        }
	    }
	    return single;
	}

修改完之后,再调用一次接口,结果如下:

Nice!就是这个单例对象创建的问题。

复盘

这个问题,前前后后花了一整天的时间,从早上搞到晚上,最终解决,简单复盘一下,分为代码问题,其他问题;

代码

java 复制代码
	private static synchronized SingleBean getSingleInstance() {
	    SingleBean single = null;
	    if(single == null){
	        single = new SingleBean();
	    }
	    return single;
	}

这段创建单例对象的代码,经分析可知:

  • static:导致了创建的对象不会被JVM回收;

  • synchronized:导致了线程的阻塞;

  • 错误的创建方式:导致每次查询都会创建大量的对象(每查一条记录就创建一个);

综合结果,就是JVM内存溢出,系统越用越慢,最终崩溃。

其他

其他原因来自两方面,场外因素和自身原因。

  • 场外因素:本身项目进度紧张,刚上测试,问题就提了一堆,而我们一个JVM溢出问题就搞了一天,产品同事也慌了,当天开了两次碰头会,又压缩了我们解决问题的时间。当时真有点"软件危机"的感觉。

  • 自身原因:排查思路开始有问题,因为页面上并不是所有接口都慢,大部分是正常的。所以我分析是两个问题,一个是慢接口,一个是导致系统崩溃的JVM内存溢出的问题,应该要优先解决后者。没想到这两个是同一个问题。另外,就是被java.lang.OutOfMemoryError唬住了,总感觉是非常不容易发现的问题,搞得我们还去找JVM的视图化工具,看JVM相关参数,但这些除了让我们知道溢出了毫无帮助。

另外

在解决问题过程中,学会了一些JVM相关的命令和工具,在这里Mark一下;

JVM 视图工具

JDK自带视图化工具,如果你安装了JDK,可在CMD窗口敲下面的命令打开;

打开后,可查看Java进程相关的JVM参数,也支持远程连接,当然需要远程那边运行Java时配置相关参数,还需要主机可被访问;

JVM 内存泄漏排查

参考下面这篇文章,当时也敲了一些命令来排查问题。

当时,我对测试环境项目敲了下图的代码,确实可以发现FGC频繁,且时长越来越长;

相关推荐
是小崔啊2 分钟前
开源轮子 - EasyExcel02(深入实践)
java·开源·excel
myNameGL34 分钟前
linux安装idea
java·ide·intellij-idea
青春男大35 分钟前
java栈--数据结构
java·开发语言·数据结构·学习·eclipse
HaiFan.1 小时前
SpringBoot 事务
java·数据库·spring boot·sql·mysql
我要学编程(ಥ_ಥ)1 小时前
一文详解“二叉树中的深搜“在算法中的应用
java·数据结构·算法·leetcode·深度优先
music0ant1 小时前
Idea 添加tomcat 并发布到tomcat
java·tomcat·intellij-idea
计算机徐师兄2 小时前
Java基于SSM框架的无中介租房系统小程序【附源码、文档】
java·微信小程序·小程序·无中介租房系统小程序·java无中介租房系统小程序·无中介租房微信小程序
源码哥_博纳软云2 小时前
JAVA智慧养老养老护理帮忙代办陪诊陪护小程序APP源码
java·开发语言·微信小程序·小程序·微信公众平台
东阳马生架构2 小时前
JVM实战—2.JVM内存设置与对象分配流转
jvm
忒可君3 小时前
C# winform 报错:类型“System.Int32”的对象无法转换为类型“System.Int16”。
java·开发语言