当考虑Java中的内存泄漏时,我们通常会考虑Java堆泄漏,即在堆中分配的对象没有被垃圾收集。这是我在处理一台服务器内存泄漏时的想法,但我即将经历的远超出我的想象。
症状:运行Vertx应用程序(没有交换分区)的生产服务器被Linux内存不足kill掉(操作系统机制,当系统出现内存紧张的情况时释放内存)崩溃。
因为它是生产服务器,所以我认为可以让我们使用堆转储和MAT来检查发生了什么,并尝试找出谁在消耗这么多内存。
结果令人惊讶,java堆是合理的,远远少于进程内存的足迹。有什么东西侵蚀了我的内存,我不知道那是什么。
我的出发点是在home目录中创建的java致命错误日志,所以我开始研究这些日志。您可以在下面的页面中关于java致命错误日志的信息:致命错误日志致命错误日志可以提供很多有价值的信息并节省时间,因此我建议您仔细阅读。致命错误日志显示堆大小小于2G,但进程正在增长到大约3-4G字节,这是怎么回事?
Java进程包含以下内存空间:
堆-分配对象的位置。
线程堆栈-包含所有线程堆栈。
Metaspace-包含元数据类(替换Java7和更早版本中的PermGen)。
代码缓存-JIT编译器代码缓存。
堆缓冲池不足。
操作系统内存-本机操作系统内存。
我用了命令:
jmap -heap [pid]
它打印了JVM堆大小的摘要,还显示了堆大小约为1.5GByte。
我检查了元空间的大小,但只有几兆字节。也许代码缓存是问题所在?我再次检查了致命错误日志,我看到只有大约20M。我得出的结论是,可能我有本机内存泄漏即-XX:NativeMemoryTracking=detail,然后使用jcmd实用程序(包含在JDK中)检查本机内存。在下一页中阅读有关NMT的更多信息:NMT结果包含以下内容:
Internal (reserved=1031767KB, committed=1031767KB)
(malloc=1031735KB #7619)
(mmap: reserved=32KB, committed=32KB)
我发现JVM有大约1Gbyte大小的内存。现在我确信我有
我发现有巨大的malloc(操作系统内存分配调用)内存分配,但我仍然不知道是什么原因造成的。我试着用gdb处理内存转储,但效果不好,我得出的结论是它可能不会有用,所以我搜索了Linux内存泄漏检测工具。我有几个候选人:
Valgrind
malloc_tracer
前两个由于某些原因不起作用,所以尝试了人们推荐的jemalloc。我在应用程序启动脚本中添加了以下行:
MALLOC_CONF=prof_leak:true,prof_final:true,lg_prof_interval:30,lg_prof_sample:17 \
LD_PRELOAD=/usr/local/lib/libjemalloc.so.2 [jre path]/jre/bin/java [app parameters]
一开始jemalloc不起作用,我不得不用configure参数-enable prof重新编译它,然后它就开始工作了。关闭应用程序后,jemalloc在工作目录中创建了reproof文件。
[jemalloc install dir]/bin/jeprof --show_bytes --pdf ‘[jre path]/jre/bin/java' [jeprof file] > [pdf output file name]
jemalloc分析表明,使用malloc os调用分配内存的“Unsafe_AllocateMemory”存在严重泄漏。我原以为我现在就能得到答案,但显然我错了。我在谷歌上搜索了一下,发现不安全的分配内存可能与名为sun.misc.Unsafe是执行本机内存分配的JDK私有类,正如它的名称所表明的那样,它是不安全的(Oracle计划在java9中删除这个类,但最终它仍保留在不受支持的模块中)。我搜索了应用程序代码,但没有找到它的任何用法,我假设它可能是应用程序的一些库使用的。主要嫌疑人是Netty,Netty是Vertx使用的网络库。Netty提供了很好的性能,但它使用本机内存分配来实现这一点。在Netty源代码中挖掘发现它正在使用`sun.misc.Unsafe`分配本机内存池。Netty包含内存泄漏检测机制,因此我尝试使用Netty参数:
-Dio.netty.leakDetection.level=advanced But that dind't supply any output.
我试着通过使用其他netty参数(没有很好的文档)来限制netty:
-Dio.netty.noPreferDirect=true
-Dio.netty.allocator.type=unpooled
-Dio.netty.maxDirectMemory=0
但这也不管用。
经过多次尝试,我最终发现直接内存分配可以通过以下jvm参数来限制:
-XX:MaxDirectMemorySize=[max memory]
在深入研究jvm之后,我终于找到了解决方案。我学到了很多关于jvm机制和内存空间的知识。