【IT168 技术文章】
Java语言相比C++的一个很大优势就是Java可以自动管理内存的回收,这大大减少了程序员的负担。然而,Java并不是杜绝了所有的内存问题,还是会有内存泄漏的问题,只不过原因和C++是不一样的,所以出现得比较少。Java的内存垃圾回收机制是从程序的主要运行对象开始检查引用链,当遍历一遍后发现没有被引用的孤立对象就作为垃圾回收。详细说明可以看ibm developerworks上的文章(http://www-900.ibm.com/developerworks/cn/java/j-leaks/index_eng.shtml)。在现在代码检查工具越来越先进的情况下,C++的内存漏洞检查已经变得容易很多,但Java的内存漏洞由于机制不一样,反而无法通过工具直接检查出来,只要靠辅助工具检测,发现疑点,然后再手动解决。
下面是我调试一个大型软件系统内存泄漏问题的过程,把其中失败的做法也写出来了,因为都是有参考价值的:
现象:程序启动后一直运行正常,但启动某个模块后,每分钟多消耗1M的内存,持续增长到出现java.lang.OutOfMemoryError为止,而CPU占用率也是逐步上升至100%。
判断:由于CPU和内存都出现问题,无法判断哪个是主因,也可能是多个原因造成的,估计最可能是多线程或者内存泄漏的问题。
辅助工具:由于不是我自己写的程序,所以没法直接估计是代码的哪一部分的问题,所以需要使用辅助工具,我比较常用的是Eclipse的插件ru.nlmk.eclipse.plugins.profiler(免费)和Borland公司的Borland Optimizeit Suite(收费)。因为我是用Eclipse来开发程序,所以使用前者比较方便,个人感觉使用时占用的系统资源少一点,而且在CPU检查方面是略强一点,但是后者在内存检查方面强大很多。
分析过程:当时由于CPU和内存都出现问题,无法判断哪个是主因,需要检测两方面的数据。内存泄漏需要等程序运行一段时间后才能看出来,所以检查很消耗时间。为了减少干扰,我把系统中所有不是必要的各线程逐个关闭来试验,然后把确认对内存问题没有影响的线程都关闭。
首先考虑解决内存问题。因为Java的内存泄漏问题往往跟HashMap相关,所以HashMap可能是一个突破口,而这么大量的内存泄漏,很可能会是某些HashMap存放过多已经没用的对象造成的(如果是这个原因,那改为用WeakHashMap就可以解决问题了)。我从HashMap和LinkedHashMap派生了子类,其中加入了数据量检查的信息,把系统中所有构造HashMap和LinkedHashMap的地方都改为使用这些子类。但是运行后发现系统中构造的HashMap只有十几个,而且每个指向的对象也不多,只有少数几个是接近1千的,而仔细检查代码后,发现这几个指向对象较多的HashMap也不会造成太大的内存泄漏问题。
这样我的思路就中断了,只好求助于工具来寻找疑点。因为发现CPU占用到100%一段时间后,程序就呈死机状态没法运行下去了,内存问题也就无从检测,所以就决定先检查CPU,用Eclipse的profiler插件来查。如图:
图1 Eclipse的profiler插件的Threads View
因为问题出在某个模块启动之后,所以这时先把统计功能关闭可以使结果更有意义(如果全部采样,初始化阶段的资源消耗对结果影响很大),也可以加快程序启动。等问题模块启动后,开始启动统计功能,此时点击Threads View中某个线程,在Thread method View中就会显示该线程中各调用的方法所运行的时间。
图2 点击Threads View中的某个Thread,Thread methods View中就会显示相关的内容