我们有一个Java应用程序,它通过JNA调用用C编写的.so库,因此Java应用程序和C代码在同一进程中运行。
我们发现了一个缓慢的内存泄漏,并通过JVM堆监视器排除了JVM堆的原因,确认问题存在于JNA或C代码中。
我们发现jemalloc可以跟踪内存分配,所以我们用--prefix=/usr/local --enable-prof
选项安装它,并设置LD_PRELOAD=/usr/local/lib/libjemalloc.so
和MALLOC_CONF=prof_leak:true,lg_prof_sample:17,prof_final:true,prof_prefix:/home/admin/prof_dump/jeprof
环境。它生成一个转储文件,然后我们使用jeprof工具,并使用jeprof --show_bytes --pdf jeprof.* > ./wdmp-profiling.pdf
将转储文件转换为pdf文件。结果显示在这里:内存泄漏档案
从图中,我们可以找到许多关于java的调用方法。但是我们找不到任何关于JNA或C代码的信息。从图中,我们发现了一个invoke分支,它只显示十六进制地址,所以我们不确定它与JNA还是C有关。
我们使用了perf-map代理工具并导出了符号映射,如下所示:符号映射
但当我们在符号映射结果中从jeprof结果中搜索十六进制地址时,我们找不到相关的符号。
在这种情况下,是否可以找到(C函数名或JNA代码(本地内存的分配位置(通过JNA调用C库的Java应用程序(,而不是Java堆内存?如果可能,怎么做?
大多数JNA内存分配都是使用Memory
类完成的。例如,这一行将使用本机malloc()
分配1024字节的内存。
Memory buffer = new Memory(1024);
许多其他JNA类,如Structure
,在内部使用Memory
缓冲区。诸如NativeLong
之类的其他类型也具有适当大小的分配。
所有这些本机内存分配最终都会作为Java垃圾收集过程的一部分发布(finalize()
调用释放内存的dispose()
(。
然而,当对本机代码进行内部分配时,如果不查看源代码,就无法查看内存的分配和释放位置。然而,如果Java/JNA代码调用分配内存的本机函数,并且在使用内存时未能遵循API关于如何释放/释放内存的指令,则这是泄漏的常见来源
一句话:你真的必须查看源代码,对于在内存上运行的每个本机函数,找出你是否负责分配内存和传递缓冲区(Java/JNA将负责释放它(,或者本机代码本身是否创建了它给你的缓冲区。如果是后一种情况,那么总会有另一种方法可以/应该用来释放缓冲区。