点击上方“匠心零度”,选择“设为星标”

做积极的人,而不是积极废人

作者:jasonGeng88

https://github.com/jasonGeng88/blog

现在很多面试官都会关心你是否有过解决内存泄漏的问题,是否有过JVM的调优经验。你如果没有经历过,该如何回答呢?希望下文对你有所帮助。

背景

前不久,上线了一个新项目,这个项目是一个压测系统,可以简单的看做通过回放词表(http请求数据),不断地向服务发送请求,以达到压测服务的目的。在测试过程中,一切还算顺利,修复了几个小bug后,就上线了。在上线后给到第一个业务方使用时,就发现来一个严重的问题,应用大概跑了10多分钟,就收到了大量的 Full GC 的告警。

针对这一问题,我们首先和业务方确认了压测的场景内容,回放的词表数量大概是10万条,回放的速率单机在 100qps 左右,按照我们之前的预估,这远远低于单机能承受的极限。按道理是不会产生内存问题的。

线上排查

首先,我们需要在服务器上进行排查。通过 JDK 自带的 jmap 工具,查看一下 JAVA 应用中具体存在了哪些对象,以及其实例数和所占大小。具体命令如下:

jmap -histo:live `pid of java`

# 为了便于观察,还是将输出写入文件
jmap -histo:live `pid of java` > /tmp/jmap00
经过观察,确实发现有对象被实例化了20多万,根据业务逻辑,实例化最多的也就是词表,那也就10多万,怎么会有20多万呢,我们在代码中也没有找到对此有显示声明实例化的地方。至此,我们需要对 dump 内存,在离线进行进一步分析,dump 命令如下:jmap -dump:format=b,file=heap.dump `pid of java

离线分析

从服务器上下载了 dump 的 heap.dump 后,我们需要通过工具进行深入的分析。这里推荐的工具有 mat、visualVM。我个人比较喜欢使用 visualVM 进行分析,它除了可以分析离线的 dump 文件,还可以与 IDEA 进行集成,通过 IDEA 启动应用,进行实时的分析应用的CPU、内存以及GC情况(GC情况,需要在visualVM中安装visual GC 插件)。工具具体展示如下(这里仅仅为了展示效果,数据不是真的):

当然,mat 也是非常好用的工具,它能帮我们快速的定位到内存泄露的地方,便于我们排查。展示如下:

场景再现经过分析,最后我们定位到是使用 httpasyncclient 产生的内存泄露问题。httpasyncclient 是 Apache 提供的一个 HTTP 的工具包,主要提供了 reactor 的 io 非阻塞模型,实现了异步发送 http 请求的功能。下面通过一个 Demo,来简单讲下具体内存泄露的原因。httpasyncclient 使用介绍:

1.maven 依赖

org.apache.httpcomponents
httpasyncclient4.1.3

2.HttpAsyncClient 客户端

public class HttpAsyncClient {

private CloseableHttpAsyncClient httpclient;

public HttpAsyncClient() {
httpclient = HttpAsyncClients.createDefault();
httpclient.start();
}

public void execute(HttpUriRequest request, FutureCallbackcallback){
httpclient.execute(request, callback);
}

public void close() throws IOException {
httpclient.close();
}

}

主要逻辑:

Demo 的主要逻辑是这样的,首先创建一个缓存列表,用来保存需要发送的请求数据。然后,通过循环的方式从缓存列表中取出需要发送的请求,将其交由 httpasyncclient 客户端进行发送。具体代码如下:  public class ReplayApplication {

public static void main(String[] args) throws InterruptedException {

//创建有内存泄露的回放客户端
ReplayWithProblem replay1 = new ReplayWithProblem();

//加载一万条请求数据放入缓存
Listcache1 = replay1.loadMockRequest(10000);//开始循环回放
replay1.start(cache1);
}
}

回放客户端实现(内存泄露):

这里以回放百度为例,创建10000条mock数据放入缓存列表。回放时,以 while 循环每100ms 发送一个请求出去。具体代码如下:" + response.getStatusLine());
}

public void failed(final Exception ex) {
System.out.println(request.getRequestLine() + "->" + ex);
}

public void cancelled() {
System.out.println(request.getRequestLine() + " cancelled");
}

});
i++;
Thread.sleep(100);
}
}

}" >public class ReplayWithProblem {

public ListloadMockRequest(int n){

Listcache = new ArrayList(n);for (int i = 0; i < n; i++) {
HttpGet request = new HttpGet("http://www.baidu.com?a="+i);
cache.add(request);
}return cache;
}public void start(Listcache) throws InterruptedException {
HttpAsyncClient httpClient = new HttpAsyncClient();int i = 0;while (true){final HttpUriRequest request = cache.get(i%cache.size());
httpClient.execute(request, new FutureCallback() {public void completed(final HttpResponse response) {
System.out.println(request.getRequestLine() + "->" + response.getStatusLine());
}public void failed(final Exception ex) {
System.out.println(request.getRequestLine() + "->" + ex);
}public void cancelled() {
System.out.println(request.getRequestLine() + " cancelled");
}
});
i++;
Thread.sleep(100);
}
}
}

内存分析:

启动 ReplayApplication 应用(IDEA 中安装 VisualVM Launcher后,可以直接启动visualvm),通过 visualVM 进行观察。1.启动情况:

2.visualVM 中前后3分钟的内存对象占比情况:

说明:$0代表的是对象本身,$1代表的是该对象中的第一个内部类。所以ReplayWithProblem$1: 代表的是ReplayWithProblem类中FutureCallback的回调类。从中,我们可以发现 FutureCallback 类会被不断的创建。因为每次异步发送 http 请求,都是通过创建一个回调类来接收结果,逻辑上看上去也正常。不急,我们接着往下看。

3.visualVM 中前后3分钟的GC情况:

从图中看出,内存的 old 在不断的增长,这就不对了。内存中维持的应该只有缓存列表的http请求体,现在在不断的增长,就有说明了不断的有对象进入old区,结合上面内存对象的情况,说明了 FutureCallback 对象没有被及时的回收。可是该回调匿名类在 http 回调结束后,引用关系就没了,在下一次 GC 理应被回收才对。我们通过对 httpasyncclient 发送请求的源码进行跟踪了一下后发现,其内部实现是将回调类塞入到了http的请求类中,而请求类是放在在缓存队列中,所以导致回调类的引用关系没有解除,大量的回调类晋升到了old区,最终导致 Full GC 产生。

核心代码分析:

代码优化

找到问题的原因,我们现在来优化代码,验证我们的结论。因为Listcache1中会保存回调对象,所以我们不能缓存请求类,只能缓存基本数据,在使用时进行动态的生成,来保证回调对象的及时回收。

代码如下:

public class ReplayApplication {

public static void main(String[] args) throws InterruptedException {

ReplayWithoutProblem replay2 = new ReplayWithoutProblem();
Listcache2 = replay2.loadMockRequest(10000);
replay2.start(cache2);
}
}
" + response.getStatusLine());
}

public void failed(final Exception ex) {
System.out.println(request.getRequestLine() + "->" + ex);
}

public void cancelled() {
System.out.println(request.getRequestLine() + " cancelled");
}

});
i++;
Thread.sleep(100);
}
}

}" >public class ReplayWithoutProblem {

public ListloadMockRequest(int n){
Listcache = new ArrayList(n);for (int i = 0; i < n; i++) {
cache.add("http://www.baidu.com?a="+i);
}return cache;
}public void start(Listcache) throws InterruptedException {
HttpAsyncClient httpClient = new HttpAsyncClient();int i = 0;while (true){
String url = cache.get(i%cache.size());final HttpGet request = new HttpGet(url);
httpClient.execute(request, new FutureCallback() {public void completed(final HttpResponse response) {
System.out.println(request.getRequestLine() + "->" + response.getStatusLine());
}public void failed(final Exception ex) {
System.out.println(request.getRequestLine() + "->" + ex);
}public void cancelled() {
System.out.println(request.getRequestLine() + " cancelled");
}
});
i++;
Thread.sleep(100);
}
}
}

结果验证

1.启动情况:

2.visualVM 中前后3分钟的内存对象占比情况:

3.visualVM 中前后3分钟的GC情况:

从图中,可以证明我们得出的结论是正确的。回调类在 Eden 区就会被及时的回收掉。old 区也没有持续的增长情况了。这一次的内存泄露问题算是解决了。

总结

关于内存泄露问题在第一次排查时,往往是有点不知所措的。我们需要有正确的方法和手段,配上好用的工具,这样在解决问题时,才能游刃有余。当然对JAVA内存的基础知识也是必不可少的,这时你定位问题的关键,不然就算工具告诉你这块有错,你也不能定位原因。

最后,关于 httpasyncclient 的使用,工具本身是没有问题的。只是我们得了解它的使用场景,往往产生问题多的,都是使用的不当造成的。所以,在使用工具时,对于它的了解程度,往往决定了出现 bug 的机率。

END

如果读完觉得有收获的话,欢迎点【好看】,关注【匠心零度】,查阅更多精彩历史!!!

让我“好看” 

idea dump分析工具_实战:OOM 后我如何分析解决的相关推荐

  1. 关键词热度分析工具_干货分享丨关键词热度分析工具

    不论我们是做竞价还是做自然优化,都需要对关键词的热度进行一番细致的分析.选择了不合适的关键词,可能就会浪费大量的时间和金钱,关键词的重要性不言而喻. 好啦,大家肯定会悄悄吐槽:谁不知道选择合适的关键词 ...

  2. python 内存分析工具_[转] python运行时内存分析工具meliae

    meliae是一个python进程内存占用监控.分析工具,它的安装需要依赖pyrex包. 一.安装: 安装python内存分析工具 sudo pip install cython sudo pip i ...

  3. java tomcat 日志分析工具_设计一个Tomcat访问日志分析工具

    常使用web服务器的朋友大都了解,一般的web server有两部分日志: 一是运行中的日志,它主要记录运行的一些信息,尤其是一些异常错误日志信息 二是访问日志信息,它记录的访问的时间,IP,访问的资 ...

  4. python日志分析工具_基于Python3的Web日志分析小工具

    PyWebLog 网站日志分析小工具 环境 Python3.5 Mysql 预览 安装 pip install pymysql pip install flask 导入日志 python Log.py ...

  5. 程序崩溃 分析工具_程序分析工具| 软件工程

    程序崩溃 分析工具 A program analysis tool implies an automatic tool that takes the source code or the execut ...

  6. 日志分析工具Awstats实战之Apache篇-多站点日志分析

    前面两篇都在讲述如何去部署nginx下的awstats日志分析工具,现在终于轮到apache.作为老牌的网页服务器,awstats对apache的支持非常完美,所以整个配置过程也是十分简单.因此,在这 ...

  7. Excel股票分析工具_每日连板清单

    Excel获取每日连板数据 对所有股票数据进行监控,选出涨停板股票清单: 可以查看昨日数据今日的涨跌情况    后端采用VBA通过API获取数据并创建前台分析链接 可以查看昨日数据在今日的涨跌情况, ...

  8. Excel股票分析工具_量化策略模拟

    在Excel中对量化策略进行模拟 根据优选算法,每日对所有股票数据进行监控和选择: 可以根据"买入日期"列判断是否当日选出股票: 后端采用VBA通过API获取数据并创建前台分析链接 ...

  9. 【转】Fiddler抓包工具手机添加代理后连不上网解决办法

    转载:Fiddler抓包工具手机添加代理后连不上网解决办法_数据结构和算法的博客-CSDN博客 最近,在工作中需要测试一个监控网络请求的SDK,需要校验该SDK记录的耗时数据的准确性.根据网上大神们提 ...

最新文章

  1. Linux下stat + 文件名后, Access,Modify,Change的含义
  2. 「修炼开始」一文带你入门深度学习
  3. 衡阳a货翡翠,南平a货翡翠
  4. 数据库集群和高可用解决方案
  5. 《剑指offer》跳台阶
  6. Firefox 10正式发布
  7. Bootstrap3 工具提示插件的事件
  8. 菜鸟驿站发布“新成长计划” 未来三年要让站点平均收入翻倍
  9. 标准模型和IE模型的区别
  10. IDEA 等常用编程软件注册码集合
  11. Redis过期删除策略
  12. Linux下的图片编辑软件
  13. linux-网络安全防火墙
  14. 人工智能-机器学习-特征工程
  15. d3js绘制y坐标轴_如何用D3绘制各类样式的x坐标轴
  16. 商城系统开发,使用微信服务号好?还是小程序?
  17. ipv4v6双栈技术_【第二十六期】IPv6系列应用篇——数据中心IPv4/IPv6双栈架构探讨...
  18. Google Voice 保留号码
  19. 能编程的游戏,你还等什么?
  20. [Python3 练习] 010 找出藏在字符串中的“密码”

热门文章

  1. 弧形背景html,JS实现带圆弧背景渐变效果的导航菜单代码
  2. matlab设计pss参考信号,[OAI][Layer1]PSS/SSS procedure
  3. Swift之父退出核心团队,自曝原因:环境有毒!
  4. 与优秀的人在一起,自己也会优秀起来!高质量技术群等你加入!
  5. 皮一皮:你的工资去哪了?实锤了!
  6. 每日一皮:曾经的你是不是也这般天真?
  7. 华为又一战略级生态启程:华为IdeaHub 使能千行百业
  8. Spring Cloud 常见面试题及答案
  9. 每日一皮:发现程序员经常熬夜有三个弊端!
  10. 这些用来审计 Kubernetes RBAC 策略的方法你都见过吗?