Java加载dll,导致Java进程内存泄露
By zhaoch
在做网络监控系统的性能测试时,出现了内存泄露的问题,困扰了很久,现在终于算是解决了,但是根本原因尚不明确,拿出来大家讨论下,看看能不能完美解决~
这个问题奇怪的地方在于是Java进程内存泄露,而不是平常的JVM内存泄露,用Jprofile等工具也无法看出问题所在。
测试代码如下:
System.loadLibrary("test1"); int threadPoolSize = 400; ExecutorService service = Executors.newFixedThreadPool(threadPoolSize); for (int i = 0; i < 400; i++) { service.submit(new Runnable() { public void run() { while (true) { try { Thread t = new Thread(); t.start(); Thread.sleep(100); } catch (Exception e) { e.printStackTrace(); } } } }); }说明:此段代码所做的工作就是加载一个dll,然后不断的启动线程(线程什么也不做,直接终止)。
注:线程池只是为了加速问题复现,无其他用处。
现象:
1.如果不加载dll,只不断的启动线程,Java进程内存正常,不会一直增长。
2.如果加载附件中test1的dll,Java进程内存会一直增长。
3.如果加载附件中test2的dll(需要安装C++运行环境vcredist_x86),Java进程内存正常,不会一直增长。
dll说明:
dll的工程源码在附件中,test1和test2的区别只在于编译选项,如附件:test1选择的是“使用标准Windows库”或“在静态库中使用MFC”,test2选择的是“在共享DLL中使用MFC”
此dll工程的特点在于使用了jni,并引入了mfc头文件【#include <afxwin.h>】,如果不引入mfc头文件则不会引起内存泄漏
目前此问题的根本原因尚不明确,怀疑是jdk的bug(使用最新的jdk1.6.0.23也没用),不知道大家有什么想法吗?欢迎大家讨论~

Thread t = new Thread();t.start();
换成
Object o = new Object();
内存照样泄露,只要加载了这个DLL,你new对象的时候,进程内存都在增长。
我估计是这个DLL做了某些行为导致这样的,至于做了什么行为,在下的C++功力不够看不出来,呵呵
Thread t = new Thread();t.start();
换成
Object o = new Object();
内存照样泄露,只要加载了这个DLL,你new对象的时候,进程内存都在增长。
我估计是这个DLL做了某些行为导致这样的,至于做了什么行为,在下的C++功力不够看不出来,呵呵
试了一下,只new Object内存不会涨,不知道你用的什么版本jdk? 我用的是jdk1.5.0_22
只new对象进程内存动都不带动的,看不出变化 26 楼 flyiclj 2011-06-28 ppgunjack 写道cyj86 写道此问题得到解决
公司技术专家回复如下:
引用
将CWinApp theApp; 放在ping.cpp文件中的最后一行即可。
1. 这不是Java的BUG
2. 这也不是MFC的BUG
3. 这也不能说是程序员造成的BUG
4. 应该是MFC框架结构的特点造成的用法问题(但MSDN上没有说明)。
CWinApp必须定义为全局变量,由C++启动代码运行。
在构造的时候,会初始化MFC中使用的全局数据。
在MFC代码appcore.cpp中可以看到在CWinApp的构造与析构函数中,
对程序使用的一些MFC架构资源进行了分配与释放。
静态链接与动态链接的一个区别是内存的共享,猜测在静态链接时,
CWinApp初始化的内容为全局共享的,在没有进行这个初始化时,
各个线程使用的资源可能会独立申请(没有释放)。
经测试,如此修改后问题不再出现
王总实在V5~
orz
线程看到的链接库资源应该是一份都是同一个地址空间,全局变量出现拷贝的情况是多进程共享
这里没有用到多进程啊,只有多线程 27 楼 ppgunjack 2011-06-28 所以全局变量我觉得解释不通
多线程不会导致共享库的全局变量的拷贝,因为这些线程处于同一虚拟地址空间,都能以相同地址访问动态共享库中的全局变量
如果是多进程访问动态共享库才会出现,每个进程访问共享库会出现拷贝共享库中定义的全局变量的情况 28 楼 flyiclj 2011-06-28 ppgunjack 写道所以全局变量我觉得解释不通
多线程不会导致共享库的全局变量的拷贝,因为这些线程处于同一虚拟地址空间,都能以相同地址访问动态共享库中的全局变量
如果是多进程访问动态共享库才会出现,每个进程访问共享库会出现拷贝共享库中定义的全局变量的情况
不过修改了之后确实好了,也许还有别的原因吧 29 楼 kingkan 2011-06-29 flyiclj 写道
试了一下,只new Object内存不会涨,不知道你用的什么版本jdk? 我用的是jdk1.5.0_22
只new对象进程内存动都不带动的,看不出变化
JDK1.6.0.22,持续创建对象情况下,每NEW GC一次,进程内存都会增长。
30 楼 flyiclj 2011-06-29 kingkan 写道flyiclj 写道
试了一下,只new Object内存不会涨,不知道你用的什么版本jdk? 我用的是jdk1.5.0_22
只new对象进程内存动都不带动的,看不出变化
JDK1.6.0.22,持续创建对象情况下,每NEW GC一次,进程内存都会增长。
用JDK1.6.0_23试了一下,未出现内存泄露,进程内存最终会稳定在固定数值
为了加速复现,我去掉了 Thread.sleep(100);,并给JVM增加了参数 -verbose:gc -Xms2m -Xmx4m
加载dll,进程内存最终稳定在22936K
不加载dll,进程内存最终稳定在22012K
不知道你是不是只看了前几次GC就确定有内存泄露了?这个要等稳定了再看吧? 31 楼 kingkan 2011-06-29 flyiclj 写道kingkan 写道flyiclj 写道
试了一下,只new Object内存不会涨,不知道你用的什么版本jdk? 我用的是jdk1.5.0_22
只new对象进程内存动都不带动的,看不出变化
JDK1.6.0.22,持续创建对象情况下,每NEW GC一次,进程内存都会增长。
用JDK1.6.0_23试了一下,未出现内存泄露,进程内存最终会稳定在固定数值
为了加速复现,我去掉了 Thread.sleep(100);,并给JVM增加了参数 -verbose:gc -Xms2m -Xmx4m
加载dll,进程内存最终稳定在22936K
不加载dll,进程内存最终稳定在22012K
不知道你是不是只看了前几次GC就确定有内存泄露了?这个要等稳定了再看吧?
嗯。。。确实如你所说,回归你之前说的线程导致增量问题,的确耐人寻味了,这位同学,有待研究。
我先去请教下大牛,嘿。
32 楼 flyiclj 2011-06-29 kingkan 写道
嗯。。。确实如你所说,回归你之前说的线程导致增量问题,的确耐人寻味了,这位同学,有待研究。
我先去请教下大牛,嘿。
可以参考一下之前的回复,这样修改确实可以解决问题,也许原因说的并不完全对,但应该也十分值得参考
cyj86 写道此问题得到解决
公司技术专家回复如下:
引用
将CWinApp theApp; 放在ping.cpp文件中的最后一行即可。
1. 这不是Java的BUG
2. 这也不是MFC的BUG
3. 这也不能说是程序员造成的BUG
4. 应该是MFC框架结构的特点造成的用法问题(但MSDN上没有说明)。
CWinApp必须定义为全局变量,由C++启动代码运行。
在构造的时候,会初始化MFC中使用的全局数据。
在MFC代码appcore.cpp中可以看到在CWinApp的构造与析构函数中,
对程序使用的一些MFC架构资源进行了分配与释放。
静态链接与动态链接的一个区别是内存的共享,猜测在静态链接时,
CWinApp初始化的内容为全局共享的,在没有进行这个初始化时,
各个线程使用的资源可能会独立申请(没有释放)。
经测试,如此修改后问题不再出现
王总实在V5~
orz
33 楼 wangyazhen 2011-06-29 建议先对dll中的方法进行压力测试 推荐.Net memory profiler工具
然后在使用Jprofiler对你的java应用程序进行跟踪
不管是java还是微软的代码 要想高效、及时的回收内存,需要在对象释放的时候显示地切掉与外部的引用,不然可能垃圾要回收好几次才能回收掉 34 楼 flyiclj 2011-06-30 wangyazhen 写道建议先对dll中的方法进行压力测试 推荐.Net memory profiler工具
然后在使用Jprofiler对你的java应用程序进行跟踪
不管是java还是微软的代码 要想高效、及时的回收内存,需要在对象释放的时候显示地切掉与外部的引用,不然可能垃圾要回收好几次才能回收掉
dll的压力测试没有做过,不过这个核心程序ping.cpp是网上的开源代码,已经好多年,看历史可以看出从98年开始,这么长的时间如果有问题,应该早就发现了吧
Jprofiler也只能跟踪JVM的内存使用吧,这个程序是进程内存泄露,JVM内存的使用是正常的,在JProfiler看不出什么问题
个人感觉还是因为JNI加载dll,才导致了Java进程内存泄露