文本来自StackOverflow问答网站的一个热门讨论:如何用Java编写一段会发生内存泄露的代码。
Q:刚才我参加了面试,面试官问我如何写出会发生内存泄露的Java代码。这个问题我一点思路都没有,好囧。
A1:通过以下步骤可以很容易产生内存泄露(程序代码不能访问到某些对象,但是它们仍然保存在内存中):
应用程序创建一个长时间运行的线程(或者使用线程池,会更快地发生内存泄露)。
线程通过某个类加载器(可以自定义)加载一个类。
该类分配了大块内存(比如new byte[1000000]),在某个静态变量存储一个强引用,然后在ThreadLocal中存储它自身的引用。分配额外的内存new byte[1000000]是可选的(类实例泄露已经足够了),但是这样会使内存泄露更快。
线程清理自定义的类或者加载该类的类加载器。
重复以上步骤。
由于没有了对类和类加载器的引用,ThreadLocal中的存储就不能被访问到。ThreadLocal持有该对象的引用,它也就持有了这个类及其类加载器的引用,类加载器持有它所加载的类的所有引用,这样GC无法回收ThreadLocal中存储的内存。在很多JVM的实现中Java类和类加载器直接分配到permgen区域不执行GC,这样导致了更严重的内存泄露。
这种泄露模式的变种之一就是如果你经常重新部署以任何形式使用了ThreadLocal的应用程序、应用容器(比如Tomcat)会很容易发生内存泄露(由于应用容器使用了如前所述的线程,每次重新部署应用时将使用新的类加载器)。
A2:
静态变量引用对象
class MemorableClass {
static final ArrayList list = new ArrayList(100);
}
调用长字符串的String.intern()
String str=readString(); // read lengthy string any source db,textbox/jsp etc.. // This will place the string in memory pool from which you cant remove str.intern(); try { BufferedReader br = new BufferedReader(new FileReader(inputFile)); ... ... } catch (Exception e) { e.printStacktrace(); } |
未关闭连接
try { Connection conn = ConnectionFactory.getConnection(); ... ... } catch (Exception e) { e.printStacktrace(); } |
JVM的GC不可达区域
比如通过native方法分配的内存。
web应用在application范围的对象,应用未重启或者没有显式移除
getServletContext().setAttribute(“SOME_MAP”, map);
web应用在session范围的对象,未失效或者没有显式移除
session.setAttribute(“SOME_MAP”, map);
不正确或者不合适的JVM选项
比如IBM JDK的noclassgc阻止了无用类的垃圾回收
A3:如果HashSet未正确实现(或者未实现)hashCode()或者equals(),会导致集合中持续增加“副本”。如果集合不能地忽略掉它应该忽略的元素,它的大小就只能持续增长,而且不能删除这些元素。