JNI引起的堆外内存泄漏问题分析

背景

客户现场的监控系统中有一个网络听诊器功能,其每隔1分钟会对全网设备进行ping操作,以此来尽可能快的发现设备及网络是否出现异常。暂且不说通过该功能来对设备及网络作健康检测是否靠谱。由于JAVA对于网络层以下的协议是无能为力的,而ping操作涉及ICMP与ARP协议,因此监控系统只能借助JNI机制来搞定。

BUG现象

监控系统的java.exe进程每隔几个小时就异常退出

问题定位

  1. 通过应用系统的日志看是否为业务相关的异常引起的 –》日志中并无任何异常信息
  2. 打开GC日志,并观察一段时间,看是否存在堆内存回收异常(泄漏或溢出) –》堆内存一切正常
  3. 此时忽然想起,java.exe进程异常退出应该会生成相关的hs_err.log文件,果然在应用目录下找到了一堆错误文件。该日志也叫crash日志。
  4. 通过查看hs_err.log内容得知,原来是jni ping引入的dll调用异常导致java.exe进程异常中止了。

    PS:如果能早点想起步骤3,那就不用浪费步骤2的功夫了。

JNI调用异常分析

JNI异常导致java进程中止的原因可能为

  • JVM自己的BUG:谷歌了一把,网上描述的BUG中,现场的JDK版本都已经修复了。
  • JNI DLL的BUG:这个原因范围就大了,至此只能根据经验猜测可能的原因,然后一个一个排除了。

由于linux环境下有这么一个机制:当内核检测到进程的物理内存不断增加至某一个值时,内核会直接将该进程kill掉。

windows是否也有这样的机制呢?目前尚未查证,还请高手解答。

在没有进一步证据的前提下,只能先猜测是否为进程物理内存出了问题,于是监测了下应用进程的物理内存损耗量,果然是缓慢递增的,但JVM堆内存仍然一切正常,由此大约知道是堆外内存使用上出了问题。

关于堆外内存的相关知识,可参考下面的文章:

至此,可以知道该问题与JAVA没啥关系了,但为了彻底搞明白,我还是硬着头皮找来DLL的C源码,想看看是否可以用我helloworld级别的C水平把这个问题搞定。

堆外内存[泄漏、异常]分析

分析C/C++应用的内存,大伙一般都会想到perftool,可惜windows环境下我始终编译不过。于是谷歌上再搜索一把”windows内存泄漏”,发现知乎上有文章推荐了一堆,但我要么下载不到,要么看不懂。最后是根据《C/C++内存泄漏及检测》介绍的方法定位到是dll中有一段代码使用了缓存导致内存泄漏,当内存达到JVM中设置的MaxDirectMemorySize值时,dll就会出现内存访问异常错误,最终导致java.exe进程异常退出了。

PS:在定位堆外内存异常相关问题时,为了快速重现问题,可以将MaxDirectMemorySize改小,MaxDirectMemorySize的默认值可认为与-Xmx设置的值一样(严格上不是,参见JVM源码分析之堆外内存完全解读

总结

该问题并非通用性问题,写这篇文章主要是为了记录下当时解决该问题的整个定位过程,文中一些知识点可能表述有误,还请批评指正。

转载请注明出处:cloudnoter.com