我刚刚接受了一次采访,被要求用Java创建内存泄漏。
不用说,我觉得自己很傻,不知道如何开始创作。
什么样的例子?
我刚刚接受了一次采访,被要求用Java创建内存泄漏。
不用说,我觉得自己很傻,不知道如何开始创作。
什么样的例子?
当前回答
我在javax.swing.JPopupMenu中遇到了非常真实的内存泄漏。
我有一个GUI应用程序,它显示多个选项卡式文档。关闭文档后,如果在选项卡上的任何组件上使用了右键单击上下文菜单,它就会在内存中停留。这些菜单在选项卡之间共享,结果是,在调用popupMenu.show(component invoker,int x,int y)后,组件会作为菜单的“调用程序”静静地存在,直到下一次更改或被setInvoker(null)清除。间接地,调用者引用持久化了整个文档以及与之相关的所有内容。
值得注意的是,菜单只能以这种方式保存对旧组件的一个引用,因此这种内存泄漏不会在没有绑定的情况下增长。
其他回答
我在javax.swing.JPopupMenu中遇到了非常真实的内存泄漏。
我有一个GUI应用程序,它显示多个选项卡式文档。关闭文档后,如果在选项卡上的任何组件上使用了右键单击上下文菜单,它就会在内存中停留。这些菜单在选项卡之间共享,结果是,在调用popupMenu.show(component invoker,int x,int y)后,组件会作为菜单的“调用程序”静静地存在,直到下一次更改或被setInvoker(null)清除。间接地,调用者引用持久化了整个文档以及与之相关的所有内容。
值得注意的是,菜单只能以这种方式保存对旧组件的一个引用,因此这种内存泄漏不会在没有绑定的情况下增长。
我想就如何使用JVM中可用的工具监视应用程序的内存泄漏提供建议。它没有显示如何生成内存泄漏,但解释了如何使用最少的可用工具检测内存泄漏。
您需要首先监视Java内存消耗。
最简单的方法是使用JVM附带的jstat实用程序:
jstat -gcutil <process_id> <timeout>
它将报告每一代(年轻、老年和老年)的内存消耗和垃圾收集时间(年轻和完整)。
一旦您发现一个完整的垃圾收集执行得太频繁并且花费了太多时间,您就可以假设应用程序正在泄漏内存。
然后需要使用jmap实用程序创建内存转储:
jmap -dump:live,format=b,file=heap.bin <process_id>
然后需要使用内存分析器(例如Eclipse memory Analyzer(MAT))分析heap.bin文件。
MAT将分析内存并向您提供有关内存泄漏的可疑信息。
我认为,一个有效的例子可能是在线程集中的环境中使用ThreadLocal变量。
例如,使用Servlet中的ThreadLocal变量与其他web组件通信,让容器创建线程,并在池中维护空闲线程。ThreadLocal变量如果没有正确清理,将一直存在,直到同一个web组件覆盖它们的值。
当然,一旦确定,问题很容易解决。
要做的一件简单的事情是使用带有不正确(或不存在)hashCode()或equals()的HashSet,然后继续添加“重复项”。而不是像应该的那样忽略重复项,集合只会增长,您将无法删除它们。
如果你想让这些坏键/元素到处乱动,你可以使用一个静态字段,比如
class BadKey {
// no hashCode or equals();
public final String key;
public BadKey(String key) { this.key = key; }
}
Map map = System.getProperties();
map.put(new BadKey("key"), "value"); // Memory leak even if your threads die.
这是一个简单/险恶的http://wiki.eclipse.org/Performance_Bloopers#String.substring.28.29.
public class StringLeaker
{
private final String muchSmallerString;
public StringLeaker()
{
// Imagine the whole Declaration of Independence here
String veryLongString = "We hold these truths to be self-evident...";
// The substring here maintains a reference to the internal char[]
// representation of the original string.
this.muchSmallerString = veryLongString.substring(0, 1);
}
}
因为子字符串指的是原始字符串的内部表示,所以原始字符串会保留在内存中。因此,只要你有一个StringLeaker在玩,你的记忆中也有整个原始字符串,即使你可能认为你只是在保存一个字符串。
避免存储对原始字符串的不需要的引用的方法如下:
...
this.muchSmallerString = new String(veryLongString.substring(0, 1));
...
为了增加坏处,您还可以.intern()子字符串:
...
this.muchSmallerString = veryLongString.substring(0, 1).intern();
...
这样做将在内存中保留原始的长字符串和派生的子字符串,即使在StringLeaker实例被丢弃之后也是如此。