记录一次线上Full GC问题排查

问题初始

线上环境运行一段时间便出现cpu爆满,导致服务不可用,无法提供正常服务。

排查过程
  • 通过top命令查看进程使用状况

发现,进程号为5525的进程cpu利用率很高。

  • 随即查看进程号为5525的进程中的线程情况,使用命令:
top -H -p 5525
复制代码

发现前四个线程cpu占用率很高,而且持有cpu时间很长,所以查看该线程是谁在使用,因为该线程号显示为十进制,所以首先将其转化为十六进制

printf "%x\n" 5530
复制代码

接着使用 jstack 命令查看该线程是谁持有

jstack 5525 | grep 159a
复制代码

发现竟然是GC占用,cpu利用率很高,而且持有cpu很长时间。继续-发现是GC的问题,接下来查看一下GC的状态信息

  • 通过 jstat查看 gc 状态(没10秒gc状态)
jstat -gcutil 5525 2000 10
复制代码

可以看出内存的年轻代和年老带的利用率都达到了惊人的100%。FGC的次数也特别多,并且在不断飙升。可以推断出 程序肯定是在哪里的实现有问题,需要重点查看大对象或者异常多的对象信息。此时可以生成headdump文件拿到本地来分析

  • 通过 jmap 命令生成dump文件,down下来进行分析
jmap -dump:format=b,file=dump.bin 5525
复制代码
  • 通过 jstack 命令生成堆栈信息
jstack -l 5525 >> jstack.out
复制代码

使用eclipse的mat工具分析dump文件,结果如下

使用方法及mat概念:www.lightskystreet.com/2015/09/01/…

点开查看 发现问题出现在阿里云的oss中 sdk 的 com.aliyun.oss.common.comm.IdleConnectionReaper 类中 根据官方说明:

www.alibabacloud.com/help/zh/doc… 然后分析代码,哪里创建的client,创建client的动作在哪里使用。

private OSSClient getOSSClient(){
		OSSClient	client	= null;
		client	= new OSSClient(endpoint, accessKeyId, accessKeySecret);
		return client;
	}
复制代码

结果发现,每次都会创建一个新的对象,创建一个新的oss的链接 然后发现使用的地方

public boolean objectExist(String path) {
    OSSClient ossClient = getOSSClient();
    return ossClient.doesObjectExist(bucketName, path);
}
复制代码

业务中涉及到文件存储的一律都是oss存储,而且由于历史原因,有些路径不是oss路径,而是本地服务器路径,所以在返回文件链接时,会进行判断,

public static String getResourceUrl(String url) {
    if (url != null && !url.trim().isEmpty()) {
    	if (url.trim().toLowerCase().startsWith("http")) {
    		return url.trim();
    	}
    	// 如果oss路径下能查到,则从oss查
    	if (url.startsWith("/")) {
    		url = url.substring(1);
    	}
    	// 注释掉去oss验证的步骤,不会有性能损耗
    	boolean objectExist = ossHelper.objectExist(url);
    	if (objectExist) {
    		return XTools.getUrl(getOssServer(), url);
    	}
    	 // 否则从本地查
    	return XTools.getUrl(getResServer(), url);
    }
    return "";
	}
复制代码

分析一下,在判断oss路径是否存在的时候,也就是调用boolean objectExist = ossHelper.objectExist(url);方法时,每次都会创建一个oss的链接,而我们的文件是很多的,所以每个文件返回访问链接的时候都会调用这个方法,所以创建的oss的链接是巨多的!!! 查询资料发现,如下网友也中招:

所以大概原因找到了,就是ossClient的链接太多了,扛不住了,所以一直在进行FGC,导致服务不可用了,最后找到相关的代码,发现有个小方法里面在每次上传或者下载的时候,都会去创建一个ossClient。修改了代码将ossClient调用的地方改成了单例。修改完线上跑了一段日子,后来也没有出现过这样的问题。

segmentfault.com/a/119000001…

代码优化

由此得出结论:优化ossHelper类中创建client的方法。并且在初始化 ossHelper 的 bean的时候,初始化创建client的连接,交给Spring管理。

private static OSSClient ossClient;
private OSSClient getOSSClient(){
    if(ossClient == null){
	synchronized (this){
	  ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
			   }
		        }
	return ossClient;
	}
复制代码

xml:

"ossHelper" class="com.ybl.crm.common.oss.OssHelper" init-method="getOSSClient">
    "0" value="${oss.endpoint}"/>
    "1" value="${oss.access.key.id}"/>
    "2" value="${oss.access.key.secret}"/>
    "3" value="${oss.bucket.name}"/>
    "4" value="${oss.server}"/>

复制代码

注解方式:@Component声明组件,@PostConstruct声明需要随组件初始化的方法.

jmeter压测验证

经jmeter压测验证

samples:请求数
average:平均耗时,单位:毫秒
error:错误率
复制代码

// 此处是每秒启动20个线程,每个线程发送10个请求
Number of threads(users) : 用户数,即线程数
Ramp-up period(in seconds) : 线程多长时间全部启动起来
Loop Count : 每个线程发送的请求数
复制代码

两千个请求,平均每个请求2.3秒,请求很快,错误率很低。 原先写法的话,当请求到达一定次数,服务器服务已经无法响应,导致错误率上升!而且cpu一直爆满,其内部维护的client满了的话,会不断的触发GC,所以就导致频繁GC。

转载于:https://juejin.im/post/5caed4d36fb9a068985fa527

你可能感兴趣的:(记录一次线上Full GC问题排查)