我刚刚接受了一次采访,被要求用Java创建内存泄漏。
不用说,我觉得自己很傻,不知道如何开始创作。
什么样的例子?
我刚刚接受了一次采访,被要求用Java创建内存泄漏。
不用说,我觉得自己很傻,不知道如何开始创作。
什么样的例子?
当前回答
我想就如何使用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将分析内存并向您提供有关内存泄漏的可疑信息。
其他回答
任何时候,只要您保留对不再需要的对象的引用,就会出现内存泄漏。请参阅处理Java程序中的内存泄漏,以了解内存泄漏如何在Java中表现出来以及您可以如何处理它。
GUI代码中的一个常见示例是创建小部件/组件并向某个静态/应用程序范围的对象添加侦听器,然后在小部件被破坏时不删除侦听器。不仅会出现内存泄漏,而且性能也会受到影响,因为无论你听什么都会引发事件,所有的老听众都会被调用。
内存泄漏的情况有很多种。我遇到了一个,它暴露了一个不应该在其他地方暴露和使用的地图。
public class ServiceFactory {
private Map<String, Service> services;
private static ServiceFactory singleton;
private ServiceFactory() {
services = new HashMap<String, Service>();
}
public static synchronized ServiceFactory getDefault() {
if (singleton == null) {
singleton = new ServiceFactory();
}
return singleton;
}
public void addService(String name, Service serv) {
services.put(name, serv);
}
public void removeService(String name) {
services.remove(name);
}
public Service getService(String name, Service serv) {
return services.get(name);
}
// The problematic API, which exposes the map.
// and user can do quite a lot of thing from this API.
// for example, create service reference and forget to dispose or set it null
// in all this is a dangerous API, and should not expose
public Map<String, Service> getAllServices() {
return services;
}
}
// Resource class is a heavy class
class Service {
}
创建一个静态映射并不断添加硬引用。这些永远不会被垃圾收集。
public class Leaker {
private static final Map<String, Object> CACHE = new HashMap<String, Object>();
// Keep adding until failure.
public static void addToCache(String key, Object value) { Leaker.CACHE.put(key, value); }
}
面试官可能在寻找一个循环引用,比如下面的代码(顺便说一下,这只会在使用引用计数的非常旧的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感知范围之外的本地资源。