我刚刚接受了一次采访,被要求用Java创建内存泄漏。
不用说,我觉得自己很傻,不知道如何开始创作。
什么样的例子?
我刚刚接受了一次采访,被要求用Java创建内存泄漏。
不用说,我觉得自己很傻,不知道如何开始创作。
什么样的例子?
当前回答
您可以使用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();
}
}
}
其他回答
就像这样!
public static void main(String[] args) {
List<Object> objects = new ArrayList<>();
while(true) {
objects.add(new Object());
}
}
创建一个只包含while true循环的JNI函数,并用另一个线程的大型对象调用它。GC不太喜欢JNI,并且会将对象永久保存在内存中。
我觉得有趣的是,没有人使用内部类示例。如果您有内部类;它固有地维护对包含类的引用。当然,从技术上讲,这不是内存泄漏,因为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假设它将永远存在,并且如果代码中无法访问它,它实际上永远不会尝试清理它。但这完全未经证实。
我认为,一个有效的例子可能是在线程集中的环境中使用ThreadLocal变量。
例如,使用Servlet中的ThreadLocal变量与其他web组件通信,让容器创建线程,并在池中维护空闲线程。ThreadLocal变量如果没有正确清理,将一直存在,直到同一个web组件覆盖它们的值。
当然,一旦确定,问题很容易解决。
Java中的内存泄漏不是典型的C/C++内存泄漏。
要了解JVM的工作原理,请阅读了解内存管理。
基本上,重要的部分是:
标记和扫描模型JRockit JVM使用标记和清除垃圾收集模型执行整个堆的垃圾收集。标记和扫描垃圾收集包括两个阶段,标记阶段和扫描阶段。在标记阶段,可以从Java访问的所有对象线程、本机句柄和其他根源标记为活动的,如以及可从这些对象访问的对象,等等向前地此过程识别并标记所有静止的对象使用,其余的可以被视为垃圾。在扫描阶段,将遍历堆以查找活动对象。这些差距记录在免费列表中可用于新对象分配。JRockit JVM使用标记和扫描的两个改进版本模型一种是同时进行标记和扫描,另一种是平行标记和扫描。你也可以将这两种策略结合起来例如主要是并发标记和并行扫描。
因此,在Java中创建内存泄漏;最简单的方法是创建一个数据库连接,做一些工作,而不是Close();然后在保持范围内的同时生成新的数据库连接。例如,这在循环中并不难做到。如果您有一个工作人员从队列中拉出并推送到数据库,那么您可以通过忘记Close()连接或在不需要时打开连接等方式轻松创建内存泄漏。
最终,您将通过忘记Close()连接来消耗已分配给JVM的堆。这将导致JVM垃圾疯狂收集;最终导致java.lang.OutOfMemoryError:java堆空间错误。应该注意,该错误可能并不意味着存在内存泄漏;这可能意味着你没有足够的记忆;例如,Cassandra和Elasticsearch等数据库可能会抛出错误,因为它们没有足够的堆空间。
值得注意的是,所有GC语言都是如此。以下是我作为SRE工作的一些例子:
Node.js使用Redis作为队列;开发团队每12小时创建一次新连接,但忘记关闭旧连接。最终,节点是OOMd,因为它消耗了所有内存。去吧(我犯了这个罪);使用JSON.Unmarshal解析大型JSON文件,然后通过引用传递结果并保持其打开状态。最终,这导致整个堆被我打开以解码JSON的意外引用所消耗。