添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

某个线上的应用运行几天后,总是出现卡死甚至出现OOM的情况。
注:文中图片可能与描述不符,仅作为演示!

通过Linux的top命令查看cpu占比

首先通过 top 命令查看,发现某个java程序占用了较高内存:

JDK的jps命令确定是哪个java程序

然后通过 jps -l 与上面的PID列(2848)比较,确定是 picasso-java-v1.jar 这个java程序占用cpu过高:

通过ps 查看具体哪个JVM线程

当时想的是可能应用内某个线程导致死循环,使用如下命令查看2848进程的各个线程小号cpu时间

//ps -mp [线程号] -o THREAD,tid,time
ps -mp 2848 -o THREAD,tid,time

下图 %CPU列 为 cpu的百分比, TID列 线程id
这里写图片描述

找到消耗cpu最大的线程(当时线上出现时某个线程消耗cpu90%多), 这里为了演示,所以取2858这个线程

通过jstack查看java中的具体线程栈信息

然后把上面线程id转化为16进制,在shell中使用 printf "%x\n" tid 即可,结果为 b2a :

然后使用jstack输出这个线程的调用栈:

//jstack [进程id] | grep [线程的16进制id] -A行数
jstack 2848 | grep b2a -A30

这里写图片描述
发现为GC线程,原来是jvm内存回收导致的cpu过高!

通过jstat查看内存回收情况

使用 jstat -gcutil 线程数 间隔秒数 次数 命令查看:

如图上面的FGC列Full GC次数为几百,而FGCT的Full GC秒数达到了几千,通过设置更多的监控次数观察,每次Full GC过后,O列的老年代还是99%,可见是内存不足导致的一直不停Full GC !

重启程序,使用-Xmx -Xms设置更大堆内存

通过重启程序, -Xmx2048m -Xms2048m 设置了更大的内存参数,缓解了问题!

问题重现,寻找其他原因,使用jmap生成堆转储文件

隔了几天后,问题重现,此时通过jmap 生成了镜像

jmap -dump:format=b,file=dumpfile.dat [pid]

生成的文件也是非常之大,达到2.1Gb!

柳暗花明,使用Eclipse Memory Analyzer分析出原因

把dump文件下载到本地,同时下载了Eclipse Memory Analyzer对dump文件进行分析。

在Eclipse Memory Analyzer中生成Leak Suspects报告:

发现是 PoolingHttpClientConnectionManager 这个类导致的。再点击上图中的Details,查看详细信息:

这下清晰了,是阿里的oss类库导致的,结合程序中的如下代码:

OSSClient ossClient = new OSSClient("","");
PutObjectResult putObjectResult = ossClient.putObject("", "", "");

这个方法在程序中没有使用单例模式而且没有关闭,每调用一次就生成了一个PoolingHttpClientConnectionManager,而且是不可回收的。通过源码查看到IdleConnectionReaper.size()这个类会生成PoolingHttpClientConnectionManager的总数量。

使用 -Xms20m -Xmx20m 运行以下程序,发现size一直变大,最后导致OOM (java.lang.OutOfMemoryError)

for (int i = 0; i < 60000; i++) {
        OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
        ossClient.putObject("<test-bucket>", "test1234" + UUID.randomUUID(), new File("d:/file.txt"));
        System.out.println("size="+IdleConnectionReaper.size());
        Thread.sleep(2);

查看api,得知使用shutdown方法即可关闭OSSClient:

ossClient.shutdown();

再运行以下程序,size一直为0,一切正常:

for (int i = 0; i < 60000; i++) {
        OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
        ossClient.putObject("<test-bucket>", "test1234" + UUID.randomUUID(), new File("d:/file.txt"));
        ossClient.shutdown();
        System.out.println("size="+IdleConnectionReaper.size());
        Thread.sleep(2);

至此,终于找到了导致cpu过高和OutOfMemoryError的真凶!

OOM,全称 Out Of Memory,意思是内存耗尽或内存溢出。对应Java 程序抛出的错为,这个错误在官方的解释如下:意思就是说,当 JVM 因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时,就会抛出这个 error(注意:这错误并非 exception,因为这个问题已经严重到不足以被应用处理)。 import org.apache.http.conn.HttpClientConnectionManager; public class IdleConnectionEvictor extends Thread { private final HttpClientConn... OkHttp ConnectionPool 对象居然有近2000个, 占用了太多的内存, 导致宕机 理论上讲, 既然都使用连接池了, 那连接池对象有1个应该就够了, 为什么这里会有近2000个? 原来是自己封装的 OkHttp 工具包的设计和使用上出了问题 该工具包支持传入 OkHttpClient 对象, 也支持创建默认的 OkHttpClient 对象, 由于小伙伴们直接使用了不传参数的方式, 导致每次调用都会新建一个 OkHttpClient 对象. 本文主要研究一下jest的IdleConnectionReaper IdleConnectionReaper jest-common-6.3.1-sources.jar!/io/searchbox/client/config/idle/IdleConnectionReaper.java public class IdleConnectionReaper extends AbstractSche... 场景:使用阿里云OSS服务的时候,debug模式下会打印无数的该信息,严重影响开发心情。 h.i.c.PoolingHttpClientConnectionManager:Closing connections idle longer than 60000 MILLISECONDS 解决方式:在日志控制文件logback-spring.xml文件里加入日志打印限制 org.apache.http.impl.conn.PoolingHttpClientConnectionManager类仅在WARN级别及 Java下载文件时,如果是小文件的下载,我们一般直接使用工具类的方法,比如cn.hutool.http.HttpUtil.downloadFile()。但是如果是大文件的下载,使用这些工具类的方法,可能会出现Out of Memory内存溢出,它是指需要的内存空间大于系统分配的内存空间,oom后果就是项目程序crash,Hprof Heap Profile 内存快照文件。 Java内存溢出是常见问题,现介绍内存溢出问题的几种解决办法,不仅适用于TongWeb,也适用所有的Java程序。问题具体原因就不再解释了,对于初学者先知道大概解决办法就行了。本文只是举例常见解决办法,实际使用中JDK版本可能稍有差异,本文无法覆盖所有的JDK版本。此类问题还是需要使用者了解JVM内存机制,请参看JDK文档。 以下介绍的这些JVM参数老版本TongWeb是加在startserver启动脚本中,新版本TongWeb是加在bin/external.vmoptions文件中,本......