JVM 内存溢出排查
  7jPfnBIFtnum 14天前 17 0

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

场景

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

JVM 内存溢出排查

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

JVM 内存溢出排查

排查

Step1:SQL问题

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

JVM 内存溢出排查

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

Step2:拦截器

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

JVM 内存溢出排查

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

JVM 内存溢出排查

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

	Cipher cipher = Cipher.getInstance(algorithmName, getSingleInstance());

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

JVM 内存溢出排查

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

Step3:单例对象

	Cipher cipher = Cipher.getInstance(algorithmName, getSingleInstance());

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

	private static synchronized SingleBean getSingleInstance() {
     
       
	    SingleBean single = null;
	    if(single == null){
     
       
	        single = new SingleBean();
	    }
	    return single;
	}

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

	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;
	}

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

JVM 内存溢出排查

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

复盘

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

代码

	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窗口敲下面的命令打开;

JVM 内存溢出排查

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

JVM 内存溢出排查

JVM 内存泄漏排查

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

  • Java诊断工具-Arthas保姆级教程

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

JVM 内存溢出排查

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 14天前 0

暂无评论

推荐阅读
  HJwyUgQ6jyHT   2024年03月22日   41   0   0 集合java
7jPfnBIFtnum
最新推荐 更多