接着上一篇《面试必问的垃圾回收(GC)》的文章继续写,上一篇主要描述了概念,这一篇主要看下代码实现。

1、静态HashMap、Vector、HashList等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致。




java list 修改属性 java修改list中的值_内存泄漏

HashList



在这个例子中,循环申请Student对象,并将所申请的对象放入一个list 中,如果仅仅释放引用本身(student=null),那么list仍然引用该对象,所以这个对象对GC 来说是不可回收的。因此,如果对象加入到list后,还必须从list中删除,最简单的方法就是将list对象设置为null。

2、HashSet中,修改对象造成Hashcode改变。




java list 修改属性 java修改list中的值_修改list中对象的值_02

hashcode




java list 修改属性 java修改list中的值_内存泄漏_03

HashSet



当集合里面的对象属性被修改后,再调用remove()方法时,不起作用。开始的结果为2,但是修改了s1后,再次添加,发现remove并没有删除掉,结果为3,原因是修改s1的年龄,此时s1元素对应的hashcode值发生改变造成的(这里可能有人测试发现remove成功了,那就是你写的bean并没有重写hashcode方法)。

3、内部类和外部模块的引用




java list 修改属性 java修改list中的值_修改list中对象的值_04

内部类



内部类的引用是比较容易遗忘的一种,而且一旦没释放可能导致一系列的后继类对象没有释放。

4、单例模式:不正确使用单例模式是引起内存泄漏的一个常见问题,单例对象在初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部的引用,那么这个对象将不能被正常回收,导致内存泄漏。例子:




java list 修改属性 java修改list中的值_内存泄漏_05

单例模式



显然Danli采用singleton模式,它持有一个Student对象的引用,而这个Student类的对象将不能被回收。想象下如果Student是个比较复杂的对象或者集合类型会发生什么情况。

5、监听器:在java编程中,我们都需要和监听器打交道,通常一个应用当中会用到很多监听器,我们会调用一个控件的诸如addXXXListener()等方法来增加监听器,但往往在释放对象的时候却没有记住去删除这些监听器,从而增加了内存泄漏的机会。

6、各种连接忘记关闭:比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,除非其显式的调用了其close()方法将其连接关闭,否则是不会自动被GC 回收的。

总结:虽然java提供了GC功能,但是凡事都没有十全十美,他还是需要程序员遵守相应的规则,才能完美执行,相辅相成,所以我们在日常开发中还是要注意垃圾回收的问题。