我刚刚接受了一次采访,被要求用Java创建内存泄漏。
不用说,我觉得自己很傻,不知道如何开始创作。
什么样的例子?
我刚刚接受了一次采访,被要求用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感知范围之外的本地资源。
其他回答
关于如何在Java中创建内存泄漏,有很多答案,但请注意采访中提出的问题。
“如何使用Java创建内存泄漏?”是一个开放式问题,其目的是评估开发人员的经验程度。
如果我问你“你有解决Java内存泄漏的经验吗?”,你的答案很简单:“是”。然后,我会继续说“你能给我举个例子来解决内存泄漏问题吗?”,你会给我一两个例子。
然而,当面试官问“如何用Java创建内存泄漏?”时,预期答案应该是以下几行:
我遇到了内存泄漏。。。(说什么时候)[这显示了我的经验]导致它的代码是。。。(解释代码)[你自己修的]我应用的修复基于。。。(解释修复)[这让我有机会询问修复的细节]我做的测试是。。。[让我有机会询问其他测试方法]我是这样记录的。。。[额外加分。如果你记录下来,那就好了]因此,有理由认为,如果我们按照相反的顺序执行,也就是说,得到我修复的代码,然后删除我的修复,我们就会出现内存泄漏。
当开发人员未能遵循这一思路时,我试图引导他/她问“你能给我一个Java如何泄漏内存的例子吗?”,然后问“你曾经修复过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假设它将永远存在,并且如果代码中无法访问它,它实际上永远不会尝试清理它。但这完全未经证实。
线程在终止之前不会被收集。它们是垃圾收集的根源。它们是少数几个不能简单地通过忘记它们或清除对它们的引用来回收的对象之一。
考虑:终止工作线程的基本模式是设置线程看到的一些条件变量。线程可以定期检查变量,并将其作为终止的信号。如果变量未声明为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的对象永远不会死。
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class Main {
public static void main(String args[]) {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
((Unsafe) f.get(null)).allocateMemory(2000000000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
您可以使用sun.misc.Unsafe类进行内存泄漏。事实上,这个服务类用于不同的标准类(例如java.nio类)。不能直接创建此类的实例,但可以使用反射来获取实例。
代码不会在EclipseIDE中编译-使用javac命令编译代码(编译期间,您会收到警告)
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import sun.misc.Unsafe;
public class TestUnsafe {
public static void main(String[] args) throws Exception{
Class unsafeClass = Class.forName("sun.misc.Unsafe");
Field f = unsafeClass.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
System.out.print("4..3..2..1...");
try
{
for(;;)
unsafe.allocateMemory(1024*1024);
} catch(Error e) {
System.out.println("Boom :)");
e.printStackTrace();
}
}
}