读书人

Java加载dll罗致Java进程内存泄露

发布时间: 2012-10-30 16:13:35 作者: rapoo

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进程内存泄露

读书人网 >编程

热点推荐