Java应用堆外内存泄露问题排查

java,应用,内存,泄露,问题,排查 · 浏览次数 : 333

小编点评

**问题原因:** 当使用java应用进行压力测试压测时,由于系统中使用的jsf接口比较多,底层都是依赖的netty,在进行大并发测试的时候,会造成内存泄漏,出现内存持续增长问题。 **解决方案:** 1. **升级jdk版本:**升级至jdk7u71版本,由于jdk7u71版本中修复了内存泄漏问题。 2. **尽量不要使用jimdb客户端的getObject和setObject方法:**如果真的需要保存对象,可以自己实现序列化和反序列化,不要解压缩功能,因为对象本来就不大,压缩不了多少空间。 3. **设置解压缩阈值:**当对象大小超过阀值之后在进行解压缩处理,不要将所有对象都进行解压缩处理处理。 4. **使用guava的DirectByteBuffer:**如果确实需要使用DirectByteBuffer进行内存分配,可以使用guava的DirectByteBuffer类进行指定大小分配,避免直接分配大对象。 5. **安装google-perftools工具:**安装google-perftools工具,可以用于分析java应用程序的内存分配情况,帮助定位内存泄漏问题。 **其他建议:** * 在进行压力测试时,可以设置线程池大小等参数,以优化性能。 * 可以使用内存监控工具,实时监控系统内存的使用情况。 * 可以尝试使用其他性能测试工具,如JProfiler等。

正文

问题是怎么发现的

最近有个java应用在做压力测试
压测环境配置:
CentOS系统 4核CPU 8g内存 jdk1.6.0_25,jvm配置-server -Xms2048m -Xmx2048m
出现问题如下
执行300并发,压测持续1个小时后内存使用率从20%上升到100%,tps从1100多降低到600多。

排查问题的详细过程

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

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

然后想到了是堆外内存泄漏,由于系统中用的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应用堆外内存泄露问题排查相似的内容:

Java应用堆外内存泄露问题排查

最近有个java应用在做压力测试,压测环境配置:CentOS系统 4核CPU 8g内存 jdk1.6.0_25,jvm配置-server -Xms2048m -Xmx2048m,出现问题,本篇文章是对此次问题的回顾和复盘

[转帖]JAVA 应用提速之 Large pages「译」

https://zhuanlan.zhihu.com/p/533305428 一、前言 我最近花了很多时间在 JVM 的内存预留代码上。它开始是因为我们得到了外部贡献,以支持在 Linux 上使用多个大小的 large page。为了以一种好的方式做到这一点,必须首先重构一些其他的东西。在沿着 内存

[转帖]JAVA 应用提速之 Large pages「译」

原文链接:https://mp.weixin.qq.com/s/HxT-DXNWkPCyDB6qAgKmXA 一、前言 我最近花了很多时间在 JVM 的内存预留代码上。它开始是因为我们得到了外部贡献,以支持在 Linux 上使用多个大小的 large page。为了以一种好的方式做到这一点,必须首先

[转帖]jvm一般相关配置OutOfMemoryError关参数配置解释

一般运行java应用都会根据实际情况设置一些jvm相关运行参数 特别是有关内存和oom溢出等参数,方便后续问题定位和解决 如常用的以下配置 nohup java -Xms256m -Xmx24g -Xmn8g -verbose:gc -XX:+PrintGCDateStamps -XX:+Print

【Azure Redis 缓存】Lettuce 连接到Azure Redis服务,出现15分钟Timeout问题

问题描述 在Java应用中,使用 Lettuce 作为客户端SDK与Azure Redis 服务连接,当遇见连接断开后,长达15分钟才会重连。导致应用在长达15分的时间,持续报错Timeout 问题解答 这是 Lettuce 目前的一个未解决的已知问题,可以查看此 github issue来了解这个

【Azure 应用服务】应用服务连接 Azure MySQL 一直失败,报错 Create connection error

问题描述 App Service上部署的Java应用,连接 Azure Database for MySQL 失败。错误信息:Create connection error, url: jdbc:mysql://....................... communications link

inno Setup 打包Java exe可执行文件和MySQL数据库,无需额外配置实现一键傻瓜式安装

前言 出现有需要打包 Java 应用和 Mysql数据库成一个安装包给出去的需求,这里我把整个打包的流程整理一下。 环境 JDK17; MySQL 5.7; 流程 Jpackage打包EXE Jpackage是JDK14后加入的一个用于独立打包的工具,能够将应用打包成exe,有了Jpackage就不

[转帖]限制容器中的jvm

在rancher中部署完java应用之后,需要对java程序的jvm进行设置,这个非常重要,不然可能会引起比较严重的后果:容器无限制的重启或者主机的内存被耗尽。 在开始之前,先来看一个问题: 在容器中跑了一个java应用,那怎么来限制这个jvm的memory呢? 按照传统的思路对memory进行限制

[转帖]【技术剖析】8. 相同版本 JVM 和 Java 应用,在 x86 和AArch64 平台性能相差30%,何故?

https://bbs.huaweicloud.com/forum/thread-168532-1-1.html 作者: 吴言 > 编者按:目前许多公司同时使用 x86 和 AArch64 2 种主流的服务器。这两种环境的算力相当,内存相同的情况下:相同版本的 JVM 和 Java 应用,相同的 J

详解JAVA线程问题诊断工具Thread Dump

摘要:Thread Dump是非常有用的诊断Java应用问题的工具。 本文分享自华为云社区《调试排错 - Java 线程分析之线程Dump分析》,作者:龙哥手记。 Thread Dump是非常有用的诊断Java应用问题的工具。每一个Java虚拟机都有及时生成所有线程在某一点状态的thread-dum