Java堆外内存泄露分析
发布日期:2021-06-30 19:32:44 浏览次数:2 分类:技术文章

本文共 7600 字,大约阅读时间需要 25 分钟。

说明:  本文登录在本人另一个博客,这个博客相对比较精练

查看堆内存占用正常,jvm垃圾回收也没有异常。而top出来显示java占用内存是几个G,那么可能想到了是堆外内存泄漏。

需要安装google-perftools工具进行分析

1.先安装g++

不然编译会报错:g++: command not found

# yum -y install gcc gcc-c++

----

2.安装libunwind

不建议版本>0.99据说有问题,这个需要FQ

# wgethttp://download.savannah.gnu.org/releases/libunwind/libunwind-0.99.tar.gz
# tar -xzvf libunwind-0.99.tar.gz
# cd libunwind-0.99
# ./configure  --prefix=/data0/java/deploy/google-perftools/local/libunwind
# make && make install
---------

3.安装gperftools

# wget https://github.com/gperftools/gperftools/releases/download/gperftools-2.5/gperftools-2.5.tar.gz

# tar -xzvf gperftools-2.5.tar.gz
# cd gperftools-2.5
# ./configure --prefix=/data0/java/deploy/google-perftools/local/gperftools-2.5/
# make && make install
-----------

4.使配置生效

# vim /etc/ld.so.conf.d/usr_local_lib.conf

新增以下内容按i

/data0/java/deploy/google-perftools/local/libunwind/lib
按esc再:wq! #保存退出
# /sbin/ldconfig  #执行此命令,使libunwind生效。 需要sudo权限
-------------------
5.加入环境变量
# export LD_PRELOAD=/data0/java/deploy/google-perftools/local/gperftools-2.5/lib/libtcmalloc.so
# export HEAPPROFILE=/data0/java/deploy/google-perftools/local/tmp/gzip
------
6.运行java程序

# java -classpath /xxx.jar com.xxx.xxx.HttpMain

就会在/data0/java/deploy/google-perftools/local/目录下生成heap文件

如果是持续运行的程序需要ctrl+c会生成dump文件。

7.分析函数调用

# /data0/java/deploy/google-perftools/local/gperftools-2.5/bin/pprof --text /usr/lib/jdk/jdk1.8.0_161/bin/java /data0/java/deploy/google-perftools/local/tmp/gzip.0001.heap

下面就是分析的函数:

也可以直接导出完整的函数到文本

# /data0/java/deploy/google-perftools/local/gperftools-2.5/bin/pprof --text /usr/lib/jdk/jdk1.8.0_161/bin/java /data0/java/deploy/google-perftools/local/tmp/gzip.0001.heap > gzip.0001.heap.log

下面选取一部分:

 

Total: 21.6 MB    17.9  82.5%  82.5%     17.9  82.5% os::malloc@9240a0     3.0  13.9%  96.4%      3.0  13.9% init     0.5   2.4%  98.8%      0.5   2.4% readCEN     0.2   0.9%  99.7%      0.2   0.9% updatewindow     0.0   0.2%  99.8%      0.0   0.2% inflateInit2_     0.0   0.1%  99.9%      0.0   0.1% _dl_new_object     0.0   0.0%  99.9%      0.0   0.0% _dl_allocate_tls     0.0   0.0%  99.9%      0.0   0.0% _nl_intern_locale_data     0.0   0.0% 100.0%      0.0   0.0% _dl_check_map_versions     0.0   0.0% 100.0%      0.0   0.0% strdup     0.0   0.0% 100.0%      0.5   2.4% ZIP_Put_In_Cache0     0.0   0.0% 100.0%      0.0   0.0% _dl_map_object_deps     0.0   0.0% 100.0%      0.0   0.0% nss_parse_service_list     0.0   0.0% 100.0%      0.0   0.0% getpwuid     0.0   0.0% 100.0%      0.0   0.0% newEntry     0.0   0.0% 100.0%      0.0   0.2% Java_java_util_zip_Inflater_init     0.0   0.0% 100.0%      0.0   0.0% __nss_database_lookup     0.0   0.0% 100.0%      0.0   0.0% expand_dynamic_string_token     0.0   0.0% 100.0%      0.0   0.0% initLoopbackRoutes     0.0   0.0% 100.0%      0.0   0.0% JLI_MemAlloc     0.0   0.0% 100.0%      0.0   0.0% __tzfile_read     0.0   0.0% 100.0%      0.0   0.1% dl_open_worker     0.0   0.0% 100.0%      0.0   0.0% add_to_global     0.0   0.0% 100.0%      0.0   0.0% add_name_to_object     0.0   0.0% 100.0%      0.0   0.0% tsearch     0.0   0.0% 100.0%      0.0   0.0% _nl_load_locale_from_archive     0.0   0.0% 100.0%      0.0   0.0% __nss_lookup_function     0.0   0.0% 100.0%      0.0   0.0% __tzstring     0.0   0.0% 100.0%      0.0   0.1% _dlerror_run     0.0   0.0% 100.0%      0.0   0.0% std::basic_string::_Rep::_S_create     0.0   0.0% 100.0%      0.0   0.0% ParseLocale     0.0   0.0% 100.0%      0.0   0.0% new_composite_name     0.0   0.0% 100.0%      0.0   0.0% __static_initialization_and_destruction_0 (inline)

 

 

参考此文:

 

 

  (说明什么是堆外内存以及怎么用堆外内存和问题)

===============

下面这个是个人博客 怕没过几天网站不存在了,所以备份下

原文地址:

 

perftools wiki地址 :

安装perftools
工具主要作用分析程序:heap、cpu等,常用于c/c++程序分析
使用文档如下:

JVM堆外内存分析

本文针对于 Oracle Hotspot jvm 虚拟机

JVM 进程占用大小一般约等于: heap + perm + thread stack + nio directbuffer
当然还有其他占用,一般情况来看native memory跟踪可以使用NMT参数 -XX:NativeMemoryTracking
详情wiki : 
JVM常见内存泄漏检查方式可以按照 oracle提供的文档: 
其中dbx是和perftools类似工具。

除此之外

进程内存在用在linux 面下top,pmap 等工具是较好确定进程内存的手段, 在/proc/${pid}/ 目录下smaps文件等查看RSS,虚拟内存,swap占用等。
pmap 找到内存申请的地址块, 可以使用gdb attach 后 dump 某一段的内存地址 ,后查看内存的数据。 注意gdb attach 进程会暂停, 线上谨慎操作。

如果发现进程内存过高,可能需要注意下程序是否存在堆外内存泄漏问题,java进程出现的堆外内存泄漏常见有如下几个方面:

  • JNI
  • NIO directbuffer 泄漏
  • gzip (本文案例就是此类)

网络编程中程序中,nio directbuffer 有监控工具可以查看, directbuffer使用量、并且可以设置最大直接内存buffer。

JVM 进程使用perftools 做分析

案例中发现的问题jetty 服务在某版本的JDK(jdk7u80~jdk8u045等 应该7u80后的版本在本文章编写时候都有)出现的内存泄漏问题分析和查找

1、安装perftools 注意配置安装目录, 找到对应的文件路径

2、启动java程序时候携带环境变量(可以参照 perftools wiki帮助 写的较为明确, 文档主要针对正对c/c++程序, Hotspot主要是c++写的)
export LD_PRELOAD=/opt/perftools/lib/libtcmalloc.so # 安装后的库文件路径
export HEAPPROFILE=/opt/mybin.hprof #heap分析文件路径和文件
特别注意目录和文件权限是否是进程用户可使用的
3、运行后启动程序后
4、pprof执行程序(注意路径),分析这些heap文件 pprof –help查看使用方式
./perftools/bin/pprof –text *.heap 以文本结果展示heap的分析文件, 结果说明见perftools wiki文档

 

Total: 55838.9 MB41320.8 74.0% 74.0% 41320.8 74.0% updatewindow9018.8 16.2% 90.2% 9018.8 16.2% inflateInit2_1559.4 2.8% 92.9% 1559.4 2.8% os::malloc@9072601556.4 2.8% 95.7% 1556.4 2.8% init587.5 1.1% 96.8% 587.5 1.1% 00007fb480508a66551.4 1.0% 97.8% 551.4 1.0% 00007f2b1d6f4a66451.5 0.8% 98.6% 451.5 0.8% 00007fb48177e8e9183.1 0.3% 98.9% 183.1 0.3% 00007fb46c1b6c91128.2 0.2% 99.1% 128.2 0.2% 00007fb480508245120.4 0.2% 99.4% 120.4 0.2% 00007f2b1d6f4245111.0 0.2% 99.6% 9129.7 16.4% Java_java_util_zip_Inflater_init102.8 0.2% 99.7% 102.8 0.2% 00007f2b1e96a8e974.2 0.1% 99.9% 74.2 0.1% readCEN45.8 0.1% 100.0% 45.8 0.1% 00007f2b09392c917.5 0.0% 100.0% 7.5 0.0% 00007fb4805025b64.5 0.0% 100.0% 4.5 0.0% 00007fb48192162d

 

 

 

.对于上述问题表现的jdk1.7.0_76不会出现

 

 

jdk1.7.0_80,jdk1.8.0_45以上明显出现。

用过pmap分析过 主要都是64m内存块较多, 查看过数据都是jar load相关的数据当时没引起注意
分析主要在updatewindow,Java_java_util_zip_Inflater_init , 这个是gzip相关, 于是搜索相关资料
结论如下:
‘’’Note that the URLClassLoader was properly disposed of, with a call to close() first, and that the ServiceLoader’s reload() method was also called to flush it’s internal cache.
This is NOT specific to the ServletContainerInitializer, it happens with any class that is loaded via the ServiceLoader.
File handles remain to the *.jar until the app’s process is stopped.’’’

网上查找相关资料

jdk 问题 
jetty issue 

处理方法,以下其一

  • JDK降级到jdk7u76, 使用不会出现问题的JDK版本
  • 等新版本jdk bug修复
  • jetty contributer 给出的方案 disable url caching wiki 

关于堆外内存:《》

DirectByteBuffer是Java用于实现堆外内存的一个重要类,我们可以通过该类实现堆外内存的创建、使用和销毁。

DirectByteBuffer该类本身还是位于Java内存模型的堆中。堆内内存是JVM可以直接管控、操纵。

而DirectByteBuffer中的unsafe.allocateMemory(size);是个一个native方法,这个方法分配的是堆外内存,通过C的malloc来进行分配的。分配的内存是系统本地的内存,并不在Java的内存中,也不属于JVM管控范围,所以在DirectByteBuffer一定会存在某种方式来操纵堆外内存。
在DirectByteBuffer的父类Buffer中有个address属性:

public abstract class Buffer {    /**     * The characteristics of Spliterators that traverse and split elements     * maintained in Buffers.     */    static final int SPLITERATOR_CHARACTERISTICS =        Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.ORDERED;    // Invariants: mark <= position <= limit <= capacity    private int mark = -1;    private int position = 0;    private int limit;    private int capacity;    // Used only by direct buffers    // NOTE: hoisted here for speed in JNI GetDirectBufferAddress    long address;

address只会被直接缓存给使用到。之所以将address属性升级放在Buffer中,是为了在JNI调用GetDirectBufferAddress时提升它调用的速率。address表示分配的堆外内存的地址。

======================

堆外内存常配合使用System GC

这里说的堆外内存主要针对java.nio.DirectByteBuffer,这些对象的创建过程会通过Unsafe接口直接通过os::malloc来分配内存,然后将内存的起始地址和大小存到java.nio.DirectByteBuffer对象里,这样就可以直接操作这些内存。这些内存只有在DirectByteBuffer回收掉之后才有机会被回收,因此如果这些对象大部分都移到了old,但是一直没有触发CMS GC或者Full GC,那么悲剧将会发生,因为你的物理内存被他们耗尽了,因此为了避免这种悲剧的发生,通过-XX:MaxDirectMemorySize来指定最大的堆外内存大小,当使用达到了阈值的时候将调用System.gc来做一次full gc,以此来回收掉没有被使用的堆外内存,具体堆外内存是如何回收的,其原理机制又是怎样的,还是后面写篇详细的文章吧

《》

转载地址:https://linuxstyle.blog.csdn.net/article/details/88032700 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:异步编程原理以及Java实现
下一篇:高性能IO模型浅析

发表评论

最新留言

很好
[***.229.124.182]2024年04月12日 03时37分21秒