问题是怎么发现的

最近有个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-09-21:有n个动物重量分别是a1、a2、a3.....an, 这群动物一起玩叠罗汉游戏, 规定从左往右选择动物,每只动物左边动物的总重量不能超过自己的重量 返回最多能选多少个动物,求一个

    2022-09-21:有n个动物重量分别是a1.a2.a3-an, 这群动物一起玩叠罗汉游戏, 规定从左往右选择动物,每只动物左边动物的总重量不能超过自己的重量 返回最多能选多少个动物,求一个高效的算 ...

  2. 2022-03-17:所有黑洞的中心点记录在holes数组里, 比如[[3,5] [6,9]]表示,第一个黑洞在(3,5),第二个黑洞在(6,9), 并且所有黑洞的中心点都在左下角(0,0),右上角(

    2022-03-17:所有黑洞的中心点记录在holes数组里, 比如[[3,5] [6,9]]表示,第一个黑洞在(3,5),第二个黑洞在(6,9), 并且所有黑洞的中心点都在左下角(0,0),右上角( ...

  3. 2021-12-15: 路径总和 III。给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。路径 不需要从根节点开

    2021-12-15: 路径总和 III.给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目.路径 不需要从根节点开 ...

  4. vue全家桶进阶之路2:JavaScript

    JavaScript(简称"JS")是当前最流行.应用最广泛的客户端脚本语言,用来在网页中添加一些动态效果与交互功能,在 Web 开发领域有着举足轻重的地位.JavaScript ...

  5. C# 客户端程序 Visual Studio 远程调试方法

    传统桌面客户端的远程调试相比UWP,ASP等项目来说,配置比较麻烦,因为它是非部署的应用程序,原理是复制编译的文件到远程计算机,通过网络来连接和VS的通信,本文主要讲述WPF,WinForm应用程序的 ...

  6. 南洋才女,德艺双馨,孙燕姿本尊回应AI孙燕姿(基于Sadtalker/Python3.10)

    孙燕姿果然不愧是孙燕姿,不愧为南洋理工大学的高材生,近日她在个人官方媒体博客上写了一篇英文版的长文,正式回应现在满城风雨的"AI孙燕姿"现象,流行天后展示了超人一等的智识水平,行文 ...

  7. 带你体验AI系列之云原生最佳实践--免费体验GPT-4教程

    前言 ​ [GPT-4]是OpenAI最新推出的大型语言模型,它支持图像和文本输入,以文本形式输出.它比GPT-3.5更大.更强.更猛.最重要的是据与研究表明,他在某些场景下,可以通过图灵测试.但是, ...

  8. 自研ORM嵌套查询和子查询,强不强大您说了算。

    测试代码 var count = 0; var refAsync = new RefAsync<int>(); //下面示例方法的重载均支持 var query = db.Query< ...

  9. [ARM汇编]计算机原理与数制基础—1.1.2 二进制与十进制数制转换

    在计算机中,我们通常使用二进制数制来表示数据,因为计算机的基本电平只有两种状态:高电平(通常表示为 1)和低电平(通常表示为 0).而在我们的日常生活中,我们习惯使用十进制数制.为了方便理解,我们需要 ...

  10. .NET指定图片地址下载并转换Base64字符串

    需求描述 需要调用第三方图片上传接口上传图片,对方图片格式只能接收Base64字符串.所以我们需要将系统服务器的图片通过Url下载下来,然后转换成Base64字符串.接下来我们将使用HttpClien ...