问题是怎么发现的

最近有个java应用在做压力测试

压测环境配置:

CentOS系统 4核CPU 8g内存 jdk1.6.0_25,jvm配置-server -Xms2048m -Xmx2048m

出现问题如下

执行300并发,压测持续1个小时后内存使用率从20%上升到100%,tps从1100多降低到600多。

排查问题的详细过程

首先使用top命令查看内存占用如下

然后查看java堆内存分布情况,查看堆内存占用正常,jvm垃圾回收也没有异常。

然后想到了是堆外内存泄漏,由于系统中用的jsf接口比较多,底层都是依赖的netty。

  • 首先考虑的是java中nio包下的DirectByteBuffer,可以直接分配堆外内存,不过该类分配的内存也有大小限制的,可以直接通过-XX:MaxDirectMemorySize=1g 进行指定,并且内存不够用的时候代码中会显式的调用System.gc()方法来触发FullGC,如果内存还是不够用就会抛出内存溢出的异常。

  • 为了验证这一想法,于是在启动参数中通过-XX:MaxDirectMemorySize=1g指定了堆外内存大小为1g,然后再次进行压测,发现内存还是在持续增长,然后超过了堆内存2g和堆外内存1g的总和,并且也没有发现有内存溢出的异常,也没有频繁的进行FullGC。所以可能不是nio的DirectByteBuffer占用的堆外内存。

为了分析堆外内存到底是谁占用了,不得不安装google-perftools工具进行分析。它的原理是在java应用程序运行时,当调用malloc时换用它的libtcmalloc.so,这样就能做一些统计了。

安装步骤如下:

  • 下载http://download.savannah.gnu.org/releases/libunwind/libunwind-0.99-beta.tar.gz,

  • ./configure

  • make

  • sudo make install //需要root权限

  • 下载http://google-perftools.googlecode.com/files/google-perftools-1.8.1.tar.gz,

  • ./configure --prefix=/home/admin/tools/perftools --enable-frame-pointers

  • make

  • sudo make install //需要root权限

  • 修改lc_config: sudo vi /etc/ld.so.conf.d/usr-local_lib.conf,加入/usr/local/lib(libunwind的lib所在目录)

  • 执行sudo /sbin/ldconfig,使libunwind生效

  • 在应用程序启动前加入:

  • export LD_PRELOAD=/home/admin/tools/perftools/lib/libtcmalloc.so

  • export HEAPPROFILE=/home/admin/heap/gzip

  • 启动应用程序,此时会在/home/admin/heap下看到诸如gzip_pid.xxxx.heap的heap文件

  • 使用/home/admin/tools/perftools/bin/pprof --text $JAVA_HOME/bin/java test_pid.xxxx.heap来查看

  • /home/admin/tools/perftools/bin/pprof --text $JAVA_HOME/bin/java gzip_22366.0005.heap > gzip-0005.txt

  • 然后查看分析结果如下

Total: 4504.5 MB
4413.9 98.0% 98.0% 4413.9 98.0% zcalloc
60.0 1.3% 99.3% 60.0 1.3% os::malloc
16.4 0.4% 99.7% 16.4 0.4% ObjectSynchronizer::omAlloc
8.7 0.2% 99.9% 4422.7 98.2% Java_java_util_zip_Inflater_init
4.7 0.1% 100.0% 4.7 0.1% init
0.3 0.0% 100.0% 0.3 0.0% readCEN
0.2 0.0% 100.0% 0.2 0.0% instanceKlass::add_dependent_nmethod
0.1 0.0% 100.0% 0.1 0.0% _dl_allocate_tls
0.0 0.0% 100.0% 0.0 0.0% pthread_cond_wait@GLIBC_2.2.5
0.0 0.0% 100.0% 1.7 0.0% Thread::Thread
0.0 0.0% 100.0% 0.0 0.0% _dl_new_object
0.0 0.0% 100.0% 0.0 0.0% pthread_cond_timedwait@GLIBC_2.2.5
0.0 0.0% 100.0% 0.0 0.0% _dlerror_run
0.0 0.0% 100.0% 0.0 0.0% allocZip
0.0 0.0% 100.0% 0.0 0.0% __strdup
0.0 0.0% 100.0% 0.0 0.0% _nl_intern_locale_data
0.0 0.0% 100.0% 0.0 0.0% addMetaName

可以看到是Java_java_util_zip_Inflater_init这个函数一直在进行内存分配,查看java源码原来是

public GZIPInputStream(InputStream in, int size) throws IOException {
super(in, new Inflater(true), size);
usesDefaultInflater = true;
readHeader(in);
}
原来是java中gzip解压缩类耗尽了系统内存,然后跟踪源码到了系统里边使用的jimdb客户端SerializationUtils类,jimdb客户端使用该工具类对保存在jimdb中的key和对象进行序列化和反序列化操作,并且在对Object类型的进行序列化和反序列化的时候用到了gzip解压缩,也就是在调用jimdb客户端的getObject和setObject方法时,内部会使用java的GZIPInputStream和GZIPOutputStream解压缩功能,当大并发进行压测的时候,就会造成内存泄漏,出现内存持续增长的问题,当压测停止后,内存也不会释放。

如何解决问题

1、升级jdk版本为jdk7u71 ,压测一段时间后,发现内存增长有所减慢,并且会稳定在一定的范围内,不会把服务器的所有内存耗尽。猜测可能是jdk1.6版本的bug

2、尽量不要使用jimdb客户端的getObject和setObject方法,如果真的需要保存对象,可以自己实现序列化和反序列化,不要解压缩功能,因为对象本来就不大,压缩不了多少空间。如真的需要解压缩功能,最好设置解压缩阀值,当对象大小超过阀值之后在进行解压缩处理,不要将所有对象都进行解压缩处理。

作者:京东零售 曹志飞

来源:京东云开发者社区

Java应用堆外内存泄露问题排查的更多相关文章

  1. Netty堆外内存泄露排查与总结

    导读 Netty 是一个异步事件驱动的网络通信层框架,用于快速开发高可用高性能的服务端网络框架与客户端程序,它极大地简化了 TCP 和 UDP 套接字服务器等网络编程. Netty 底层基于 JDK ...

  2. 解Bug之路-记一次JVM堆外内存泄露Bug的查找

    解Bug之路-记一次JVM堆外内存泄露Bug的查找 前言 JVM的堆外内存泄露的定位一直是个比较棘手的问题.此次的Bug查找从堆内内存的泄露反推出堆外内存,同时对物理内存的使用做了定量的分析,从而实锤 ...

  3. Java堆外内存之六:堆外内存溢出问题排查

    一.堆外内存组成 通常JVM的参数我们会配置 -Xms 堆初始内存 -Xmx 堆最大内存 -XX:+UseG1GC/CMS 垃圾回收器 -XX:+DisableExplicitGC 禁止显示GC -X ...

  4. Java堆外内存之七:JVM NativeMemoryTracking 分析堆外内存泄露

    Native Memory Tracking (NMT) 是Hotspot VM用来分析VM内部内存使用情况的一个功能.我们可以利用jcmd(jdk自带)这个工具来访问NMT的数据. NMT介绍 工欲 ...

  5. 一次完整的JVM堆外内存泄漏故障排查记录

    前言 记录一次线上JVM堆外内存泄漏问题的排查过程与思路,其中夹带一些JVM内存分配机制以及常用的JVM问题排查指令和工具分享,希望对大家有所帮助. 在整个排查过程中,我也走了不少弯路,但是在文章中我 ...

  6. Java进程堆外内存(off heap)大小

    一.使用ByteBuffer.allocateDirect分配的off heap内存大小 本机进程 在Jvisualvm中安装 Mbeans插件.然后查看java.nio/BufferPool/dir ...

  7. JAVA使用堆外内存导致swap飙高

    https://github.com/nereuschen/blog/issues/29 堆内内存分析一般用Memory Analyzer Tool http://tivan.iteye.com/bl ...

  8. Java NIO 堆外内存与零拷贝

    一.直接缓存 这个例子的区别就是 ByteBuffer.allocateDirect(512); 进入allocateDirect方法 进入DirectByteBuffer构造函数 Native方法: ...

  9. Netty堆外内存泄漏排查,这一篇全讲清楚了

    上篇文章介绍了Netty内存模型原理,由于Netty在使用不当会导致堆外内存泄漏,网上关于这方面的资料比较少,所以写下这篇文章,专门介绍排查Netty堆外内存相关的知识点,诊断工具,以及排查思路提供参 ...

  10. Java堆外内存之三:堆外内存回收方法

    一.JVM内存的分配及垃圾回收 对于JVM的内存规则,应该是老生常谈的东西了,这里我就简单的说下: 新生代:一般来说新创建的对象都分配在这里. 年老代:经过几次垃圾回收,新生代的对象就会放在年老代里面 ...

随机推荐

  1. 2022-01-26:最优账单平衡。 一群朋友在度假期间会相互借钱。比如说,小爱同学支付了小新同学的午餐共计 10 美元。如果小明同学支付了小爱同学的出租车钱共计 5 美元。我们可以用一个三元组 (x

    2022-01-26:最优账单平衡. 一群朋友在度假期间会相互借钱.比如说,小爱同学支付了小新同学的午餐共计 10 美元.如果小明同学支付了小爱同学的出租车钱共计 5 美元.我们可以用一个三元组 (x ...

  2. Alist云盘视频加密助手:支持云盘视频文件加密与在线播放,不用再担心视频文件被和谐了!

    在当前娱乐资源丰富的时代,人们每天都在接触各种视频资源.然而,网盘限速.版权审核.视频分级.少儿不宜等问题经常让人感到困扰.如何在保护隐私的前提下,让视频存储和分享变得更加便捷.安全呢?分享一款实用的 ...

  3. 玩转服务器之环境篇:PHP和Python环境部署指南

    前几篇文章中讲解了如何搭建docker和Java Web环境的方法,本篇文章来教大家搭建一个好的PHP和Python环境,可以帮助开发和运行PHP和Python应用程序,使其更加高效和稳定. 一. P ...

  4. AccessToken、for_user、get_token

    在Django REST framework的SimpleJWT库中,AccessToken是一个类,用于表示一个JSON Web Token (JWT)中的访问令牌部分.访问令牌是一种常见的身份验证 ...

  5. 华为Atlas 200I DK A2开箱!

    摘要:Atlas 200I DK A2是Atlas 200DK之后的一款产品,从2022年一直酝酿至今,终于在2023年5月6日-7日昇腾AI开发者峰会2023正式发布. 本文分享自华为云社区< ...

  6. 从零开始使用 Astro 的实用指南

    在这个实用的Astro指南中,我将指导你完成设置过程,并告诉你如何构造你的文件.你将学习如何添加页面.交互式组件,甚至是markdown文章.我还会告诉你如何从服务器上获取数据,创建布局,并使用van ...

  7. python -- pandas常见的一些行、列操作方法(感兴趣的,可以跟着一起练练手)

    这篇文章分享一下pandas常见的一些行.列操作方法,比较基础,感兴趣的童鞋可以看看. 首先,我们用 "random.seed(int i)" 生成一组测试数据. 对于random ...

  8. 在技术圈混,不知道这些你就 OUT 了

    技术之路就是漫长的打怪升级,不断学习前人的踩坑经验才能提升自己,切忌闭门造车.所谓独乐乐不如众乐乐,强烈推荐以下公众号,让你的生活多点意思,希望小伙伴们能在 coding 的路上走的更远. OSC开源 ...

  9. 800名科学家Nature联名发文主张废除p值!

    每年,全球有数百万学生修读统计学课程.随着世界上的数据量越来越大,统计学已成为越来越受欢迎的话题.如果大多数学生都从这门课中记住一点,那可能就是"统计显著性"和"p 值& ...

  10. Bioconductor 中的 R 包安装教程

    Bioconductor 是一个基于 R 语言的生物信息软件包,主要用于生物数据的注释.分析.统计.以及可视化(http://www.bioconductor.org). 总所周知,Bioconduc ...