我刚刚接受了一次采访,被要求用Java创建内存泄漏。
不用说,我觉得自己很傻,不知道如何开始创作。
什么样的例子?
我刚刚接受了一次采访,被要求用Java创建内存泄漏。
不用说,我觉得自己很傻,不知道如何开始创作。
什么样的例子?
当前回答
我觉得有趣的是,没有人使用内部类示例。如果您有内部类;它固有地维护对包含类的引用。当然,从技术上讲,这不是内存泄漏,因为Java最终会清理掉它;但这会导致类停留的时间比预期的长。
public class Example1 {
public Example2 getNewExample2() {
return this.new Example2();
}
public class Example2 {
public Example2() {}
}
}
现在,如果您调用Example1并得到一个Example2丢弃Example1,那么您本质上仍然有一个到Example1对象的链接。
public class Referencer {
public static Example2 GetAnExample2() {
Example1 ex = new Example1();
return ex.getNewExample2();
}
public static void main(String[] args) {
Example2 ex = Referencer.GetAnExample2();
// As long as ex is reachable; Example1 will always remain in memory.
}
}
我还听到一个传言,如果你有一个变量存在的时间超过了一个特定的时间;Java假设它将永远存在,并且如果代码中无法访问它,它实际上永远不会尝试清理它。但这完全未经证实。
其他回答
面试官可能在寻找一个循环引用,比如下面的代码(顺便说一下,这只会在使用引用计数的非常旧的JVM中泄漏内存,而现在情况已经不是这样了)。但这是一个非常模糊的问题,因此这是展示您对JVM内存管理理解的绝佳机会。
class A {
B bRef;
}
class B {
A aRef;
}
public class Main {
public static void main(String args[]) {
A myA = new A();
B myB = new B();
myA.bRef = myB;
myB.aRef = myA;
myA=null;
myB=null;
/* at this point, there is no access to the myA and myB objects, */
/* even though both objects still have active references. */
} /* main */
}
然后您可以解释,使用引用计数,上面的代码会泄漏内存。但大多数现代JVM不再使用引用计数。大多数都使用一个清理垃圾收集器,它实际上会收集这些内存。
接下来,您可能会解释创建一个具有底层本机资源的Object,如下所示:
public class Main {
public static void main(String args[]) {
Socket s = new Socket(InetAddress.getByName("google.com"),80);
s=null;
/* at this point, because you didn't close the socket properly, */
/* you have a leak of a native descriptor, which uses memory. */
}
}
然后您可以解释这在技术上是内存泄漏,但实际上泄漏是由JVM中的本机代码分配底层本机资源造成的,而Java代码没有释放这些资源。
最后,对于现代JVM,您需要编写一些Java代码来分配JVM感知范围之外的本地资源。
对前面的答案有一点改进(为了更快地生成内存泄漏),就是使用从大型XML文件加载的DOM文档实例。
要做的一件简单的事情是使用带有不正确(或不存在)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.
面试官可能一直在寻找一个循环参考解决方案:
public static void main(String[] args) {
while (true) {
Element first = new Element();
first.next = new Element();
first.next.next = first;
}
}
这是引用计数垃圾收集器的典型问题。然后,您可以礼貌地解释JVM使用了一种更复杂的算法,它没有这种限制。
这是一个简单/险恶的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实例被丢弃之后也是如此。