记“JNA加载dll失败”排查

539 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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左右。

image.png

现在知道了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,搜索加载的动态库名称

image.png

2、计算

0x000000005c1df000-  0x0000000059f40000=0x229 F000=36,302,848约为34M

唉,无用的知识好像又增加了