我刚刚接受了一次采访,被要求用Java创建内存泄漏。
不用说,我觉得自己很傻,不知道如何开始创作。
什么样的例子?
我刚刚接受了一次采访,被要求用Java创建内存泄漏。
不用说,我觉得自己很傻,不知道如何开始创作。
什么样的例子?
当前回答
Java内存泄漏的一个例子是当忘记调用ResultSets close方法时导致的MySQL内存泄漏错误。例如:
while(true) {
ResultSet rs = database.select(query);
...
// going to next step of loop and leaving resultset without calling rs.close();
}
其他回答
线程在终止之前不会被收集。它们是垃圾收集的根源。它们是少数几个不能简单地通过忘记它们或清除对它们的引用来回收的对象之一。
考虑:终止工作线程的基本模式是设置线程看到的一些条件变量。线程可以定期检查变量,并将其作为终止的信号。如果变量未声明为volatile,那么线程可能看不到对变量的更改,因此它不知道终止。或者想象一下,如果一些线程想要更新共享对象,但在试图锁定该对象时出现死锁。
如果您只有少数线程,这些错误可能会很明显,因为您的程序将停止正常工作。如果您有一个线程池,可以根据需要创建更多线程,那么过时/卡住的线程可能不会被注意到,并且会无限累积,从而导致内存泄漏。线程可能会在应用程序中使用其他数据,因此也会阻止收集它们直接引用的任何数据。
作为玩具示例:
static void leakMe(final Object object) {
new Thread() {
public void run() {
Object o = object;
for (;;) {
try {
sleep(Long.MAX_VALUE);
} catch (InterruptedException e) {}
}
}
}.start();
}
可以任意调用System.gc(),但传递给leakMe的对象永远不会死。
一些建议:
在servlet容器中使用commons日志记录(可能有点挑衅)在servlet容器中启动线程,不要从其运行方法返回在servlet容器中加载动画GIF图像(这将启动一个动画线程)
通过重新部署应用程序,可以“改善”上述效果;)
我最近偶然发现:
调用“newjava.util.zip。充气器();”而不调用“充气器.end()”
阅读http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5072161并将问题联系起来进行深入讨论。
GUI代码中的一个常见示例是创建小部件/组件并向某个静态/应用程序范围的对象添加侦听器,然后在小部件被破坏时不删除侦听器。不仅会出现内存泄漏,而且性能也会受到影响,因为无论你听什么都会引发事件,所有的老听众都会被调用。
也许通过JNI使用外部本机代码?
使用纯Java,这几乎是不可能的。
但这是一种“标准”类型的内存泄漏,即您无法再访问内存,但它仍然属于应用程序。相反,您可以保留对未使用对象的引用,或者打开流而不关闭它们。
下面将有一个不明显的Java泄漏案例,除了被遗忘的侦听器、静态引用、哈希图中的伪/可修改键,或者只是线程被卡住而没有机会结束其生命周期的标准案例之外。
File.deleteOnExit()-总是泄漏字符串,如果字符串是子字符串,则泄漏更严重(底层的char[]也泄漏)-在Java 7中,子字符串也会复制char[],因此后者不适用@丹尼尔,不过不需要投票。
我将集中讨论线程,以展示非托管线程的危险性,甚至不希望触及摆动。
Runtime.addShutdownHook,不删除。。。然后,即使使用removeShutdownHook,由于ThreadGroup类中关于未启动线程的错误,它也可能无法被收集,从而有效地泄漏了ThreadGroup。JGroup在GossipRouter中有漏洞。创建一个线程,但不是启动它,它属于与上面相同的类别。创建线程继承ContextClassLoader和AccessControlContext,加上ThreadGroup和任何InheritedThreadLocal,所有这些引用都是潜在的泄漏,以及类加载器加载的所有类和所有静态引用,以及ja-ja。这种效果在整个j.u.c.Executor框架中尤其明显,该框架具有超简单的ThreadFactory接口,但大多数开发人员对潜在的危险一无所知。此外,许多库确实会根据请求启动线程(太多行业流行的库)。ThreadLocal缓存;这些在很多情况下都是邪恶的。我相信每个人都看到过很多基于ThreadLocal的简单缓存,但坏消息是:如果线程在上下文ClassLoader的生命周期中继续运行超过预期,这是一个非常好的小泄漏。除非确实需要,否则不要使用ThreadLocal缓存。当ThreadGroup本身没有线程,但仍保留子ThreadGroups时,调用ThreadGroup.destroy()。一个严重的泄漏,将阻止ThreadGroup从其父级中删除,但所有子级都无法枚举。使用WeakHashMap和值(in)直接引用键。如果没有堆转储,这很难找到。这适用于可能将硬引用保留回受保护对象的所有扩展弱/软引用。将java.net.URL与HTTP(S)协议一起使用,并从(!)加载资源。这一个是特殊的,KeepAliveCache在系统ThreadGroup中创建了一个新线程,该线程泄漏了当前线程的上下文类加载器。当不存在活动线程时,线程会在第一个请求时创建,因此您可能会幸运,或者只是泄漏。泄漏在Java7中已经修复,创建线程的代码正确地删除了上下文类加载器。创建类似线程的情况很少(如ImageFetcher,也已修复)。使用充气器InputStream在构造函数(例如PNGImageDecoder)中传递新的java.util.zip充气器(),而不调用充气器的end()。好吧,如果你只传递一个新的构造函数,就没有机会。。。是的,如果将其作为构造函数参数手动传递,则对流调用close()不会关闭充气机。这不是真正的泄漏,因为它将由终结器释放。。。当它认为有必要时。直到那一刻,它会严重消耗本地内存,导致Linux oom_killer肆无忌惮地终止进程。主要的问题是,在Java中完成是非常不可靠的,G1使其更糟,直到7.0.2。故事的寓意:尽快释放本土资源;终结器太差了。与java.util.zip.Deflater的情况相同。这一情况更糟,因为Deflater在java中需要内存,即总是使用15位(最大值)和8个内存级别(最大值为9)来分配数百KB的本地内存。幸运的是,Deflater没有被广泛使用,据我所知,JDK没有任何误用。如果手动创建放气器或充气器,请始终调用end()。最后两种方法中最棒的一点是:您无法通过常规的分析工具找到它们。
(我可以根据要求再添加一些我遇到的时间浪费者。)
祝你好运,保持安全;泄漏是邪恶的!