持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天,点击查看活动详情
关于Jna,简单说就是一个可以允许java调用C而不写适配代码的开源项目,需要详细了解的同学请看这里:JNA
故障描述:
jna.jar 中的Native.loadLibarary()方法加载dll,抛出异常:UnsatisfiedLinkError:Unable to load library 。。。。( 此处为libraryName)
环境:window 32位XP,内存2G JDK1.6
故障定位:
该问题在解码测试过程中时隐时现,一直没抓到故障出现的真正原因。但问题一直存在,严重影响了工作的心情。
后来痛下决心,一行行的分析代码,经过多次尝试,终于还原了案发现场,找到JNA中最初始的报错原因。
真相就在 jna的JAR包中:类 NativeLibrary 的191行执行过程中, 其中Native.open方法为 声明为 static native long open (String name ,int flags),即为JNA内部调用的dll方法。
JNA内部在加载dll时报错:提示 “存储空间不足,无法加载”,由此很清楚了解到故障原因,为存储空间不足。
那么问题来了,为什么会存储空间不足?
java调用的dll会加载到java同一个进程中,我们需要首先补充一点windows进程内存的知识:Windows 中的 RAM、 虚拟内存、 页面文件和内存管理。简单来说,32位系统中,进程可用的内存为2G。对于一个在2G内存的windows运行的java进程来说,可用的内存就更加捉襟见肘了。
下图以客户端jvm配置为例分析,java进程的内存使用大概如下图所示:(忽略程序计数器等小内存区域)JNI 等dll可用内存只有200M左右。
现在知道了dll可用的内存大小为200M左右,那我们只要确定dll加载后占用内存大小,就可以揭示故障的真相了。
然而问题又来了,我们如何才能确定dll加载后占用的内存大小呢?
dll所在的内存区域既不是堆,也不是非堆,更不是什么栈,jconsole.exe等内存监控工具有心无力,那怎么办?
冥思苦相中。。。。忽然想起java进程崩溃时生成的hs_err_pidXXX.log中记录了各种内存地址信息,那也应该有加载的动态库的地址吧,马上动手构造个java进程crash的场景,生成hs_err_pidXXX.log 。具体就是加载dll后,使用Unsafe类在直接操作内存,造成java进程crash。具体代码
public class LibraryTest {
public static void main(String[] args) throws Exception {
String fileName = "dll_file_path";
System.load(fileName);
Unsafe unsafe = new LibraryTest().getUnsafeInstance();
long addr = unsafe.allocateMemory(0);
System.out.println("unsafe address :" + addr);
unsafe.putByte(addr + 1, (byte) 6);
}
public Unsafe getUnsafeInstance() throws Exception {
// 通过反射获取rt.jar下的Unsafe类
Field theUnsafeInstance = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafeInstance.setAccessible(true);
return (Unsafe) theUnsafeInstance.get(Unsafe.class);
}
}
打开生成log文件,果然在Dynamic libraries 中记录了dll地址的起止信息。经过计算,两个解码库同时加载的时候内存大小接近300M。至此真相大白,优化dll内存占用成为解决故障的关键。
内存占用大小计算详细过程。
1、执行以上代码后,找到对应的crash日志:hs_err_pidxxxx.log,搜索加载的动态库名称
2、计算
0x000000005c1df000- 0x0000000059f40000=0x229 F000=36,302,848约为34M
唉,无用的知识好像又增加了