我刚刚接受了一次采访,被要求用Java创建内存泄漏。
不用说,我觉得自己很傻,不知道如何开始创作。
什么样的例子?
我刚刚接受了一次采访,被要求用Java创建内存泄漏。
不用说,我觉得自己很傻,不知道如何开始创作。
什么样的例子?
当前回答
GUI代码中的一个常见示例是创建小部件/组件并向某个静态/应用程序范围的对象添加侦听器,然后在小部件被破坏时不删除侦听器。不仅会出现内存泄漏,而且性能也会受到影响,因为无论你听什么都会引发事件,所有的老听众都会被调用。
其他回答
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();
}
}
}
创建一个只包含while true循环的JNI函数,并用另一个线程的大型对象调用它。GC不太喜欢JNI,并且会将对象永久保存在内存中。
内存泄漏的情况有很多种。我遇到了一个,它暴露了一个不应该在其他地方暴露和使用的地图。
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 {
}
我最近遇到了一种更微妙的资源泄漏。我们通过类加载器的getResourceAsStream打开资源,但碰巧输入流句柄没有关闭。
嗯,你可能会说,真是个白痴。
嗯,有趣的是:通过这种方式,您可以泄漏底层进程的堆内存,而不是JVM的堆内存。
您只需要一个jar文件,其中包含一个将从Java代码中引用的文件。jar文件越大,分配内存的速度越快。
您可以使用以下类轻松创建这样的jar:
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class BigJarCreator {
public static void main(String[] args) throws IOException {
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File("big.jar")));
zos.putNextEntry(new ZipEntry("resource.txt"));
zos.write("not too much in here".getBytes());
zos.closeEntry();
zos.putNextEntry(new ZipEntry("largeFile.out"));
for (int i=0 ; i<10000000 ; i++) {
zos.write((int) (Math.round(Math.random()*100)+20));
}
zos.closeEntry();
zos.close();
}
}
只需粘贴到名为BigJarCreator.java的文件中,从命令行编译并运行它:
javac BigJarCreator.java
java -cp . BigJarCreator
等等:您在当前工作目录中找到一个jar存档,其中包含两个文件。
让我们创建第二个类:
public class MemLeak {
public static void main(String[] args) throws InterruptedException {
int ITERATIONS=100000;
for (int i=0 ; i<ITERATIONS ; i++) {
MemLeak.class.getClassLoader().getResourceAsStream("resource.txt");
}
System.out.println("finished creation of streams, now waiting to be killed");
Thread.sleep(Long.MAX_VALUE);
}
}
这个类基本上什么都不做,只创建未引用的InputStream对象。这些对象将立即被垃圾收集,因此不会影响堆大小。对于我们的示例来说,从jar文件加载现有资源很重要,这里的大小很重要!
如果您有疑问,请尝试编译并启动上面的类,但确保选择了合适的堆大小(2MB):
javac MemLeak.java
java -Xmx2m -classpath .:big.jar MemLeak
在这里您不会遇到OOM错误,因为没有保留引用,所以无论您在上面的示例中选择了多大的ITERATIONS,应用程序都将继续运行。除非应用程序执行wait命令,否则进程的内存消耗(在顶部(RES/RSS)或进程资源管理器中可见)会增加。在上面的设置中,它将分配大约150 MB的内存。
如果希望应用程序安全运行,请在创建输入流的位置关闭输入流:
MemLeak.class.getClassLoader().getResourceAsStream("resource.txt").close();
并且您的进程不会超过35MB,与迭代计数无关。
非常简单和令人惊讶。
理论上你不能。Java内存模型阻止了这一点。但是,因为必须实现Java,所以可以使用一些警告。这取决于您可以使用什么:
如果可以使用本机,则可以分配以后不会放弃的内存。如果这是不可用的,那么Java有一个不为人知的小秘密。您可以请求一个不由GC管理的直接访问数组,因此可以很容易地用于造成内存泄漏。这由DirectByteBuffer提供(http://download.oracle.com/javase/1.5.0/docs/api/java/nio/ByteBuffer.html#allocateDirect(int))。如果不能使用其中任何一个,仍然可以通过欺骗GC来造成内存泄漏。JVM是使用一代垃圾收集来实现的。这意味着垃圾堆被划分为三个区域:年轻人、成年人和老年人。对象创建时从年轻区域开始。随着它被越来越多地使用,它逐渐发展到成人到老年人。最有可能到达接骨木区域的对象不会被垃圾收集。您无法确定对象是否泄漏,如果您请求停止并清理GC,它可能会清理它,但在很长一段时间内,它会泄漏。更多信息请访问(http://java.sun.com/docs/hotspot/gc1.4.2/faq.html)此外,类对象不需要是GC’ed。也许有办法做到这一点。