今天在做项目的时候,因为需要调用C++同事的本地动态库XXXX.dll 文件,所以引用了jna.
jna介绍
JNA(Java Native Access )提供一组Java工具类用于在运行期间动态访问系统本地库(native library:如Window的dll)而不需要编写任何Native/JNI代码。开发人员只要在一个java接口中描述目标native library的函数与结构,JNA将自动实现Java接口到native function的映射。
实际上就是不需要自己写额外的代码就能正常访问本地c++ 代码方法的Java封装。
如何调用,可以参考网上的教程或者案例,在此不做讲述。
问题再现
我是用的是封装过的jar 包,用来调用dll 文件中的动态方法。在创建实例对象的时候,都没有问题。但是在程序运行一段时间后,系统直接退出,并且显示:
Process finished with exit code -1073740940 (0xC0000374)
这句话的意思实际上是:垃圾回收器在尝试回收内存时遇到了异常,从而导致程序停止运行。
但是具体什么问题导致的停止,却没有显示。内存溢出的可能性十分大。
于是乎,我查看jconsole , 发现内存波动很正常,好像并没有什么大问题。
但是当我手动点击GC的时候,系统突然崩掉。并且打印一堆JVM错误信息:
A fatal error has been detected by the Java Runtime Environment:
EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x00007ffd8c6b5bb6, pid=13076, tid=0x0000000000002a78
JRE version: Java(TM) SE Runtime Environment (8.0_331-b09) (build 1.8.0_331-b09)
Java VM: Java HotSpot(TM) 64-Bit Server VM (25.331-b09 mixed mode windows-amd64 compressed oops)
Problematic frame:
C [ntdll.dll+0x25bb6]
注意,这里报错显示EXCEPTION_ACCESS_VIOLATION,这个错误是访问异常导致。
具体执行文件是本地的 ntdll.dll 文件,该文件主要是和堆有关。
于是乎 ,我点击进去查看 jar包的调用形式:
public class Image implements AutoCloseable {
private Pointer ptr = new Memory(1048576L);
public Image(String path) throws UnsupportedFormatException {
try {
port.load(this.ptr);
boolean support = port.can_be_file(this.ptr, path);
if (!support) {
this.close();
throw new UnsupportedFormatException("不支持的文件格式");
} else {
///...可以用
}
} catch (Exception var3) {
this.close();
throw var3;
}
}
}
貌似new 构造函数的时候并没有啥问提,一开始以为是c++ 那里的问题,可回头想,可能这边定义有误,要不然怎么是过了一会儿就死掉了?
于是乎继续看下去:
// 释放内存
public void close() {
long peer = Pointer.nativeValue(ptr);
Native.free(peer);
Pointer.nativeValue(ptr,0L);
}
这个实际上也没有问题。但是!! JVM报错中有一个很重要的信息:
Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
j com.sun.jna.Native.free(J)V+0
j com.sun.jna.Memory.free(J)V+7
j com.sun.jna.Memory$MemoryDisposer.run()V+4
j com.sun.jna.internal.Cleaner$CleanerRef.clean()V+15
j com.sun.jna.internal.Cleaner$1.run()V+22
v ~StubRoutines::call_stub
虚拟机在处理的时候,调用了Memory.free() 方法了,free 下面又调用了Native.free 。
这个时候,结合上面的代码突然发现,这个释放好像是走了两边?? 于是乎 我把指针 ptr 置为null:
this.ptr = null;
再次运行项目,就正常了。因为置为null 所以JVM就知道这个变量实际是一个可回收的变量。
所以,这个错误很有可能是JVM在进行清理的时候,出现二次释放的问题导致的!