我刚刚接受了一次采访,被要求用Java创建内存泄漏。

不用说,我觉得自己很傻,不知道如何开始创作。

什么样的例子?


当前回答

我最近遇到了由log4j引起的内存泄漏情况。

Log4j有一种称为嵌套诊断上下文(NDC)的机制,它是一种区分不同来源的交织日志输出的工具。NDC工作的粒度是线程,因此它区分不同线程的日志输出。

为了存储线程特定的标记,log4j的NDC类使用一个Hashtable,该Hashtable由thread对象本身(而不是线程id)键控,因此直到NDC标记保留在内存中,挂在线程对象上的所有对象也保留在内存。在我们的web应用程序中,我们使用NDC标记带有请求id的登录,以将日志与单个请求区分开来。将NDC标记与线程关联的容器在返回请求响应时也会将其删除。在处理请求的过程中,产生了一个子线程,类似于以下代码:

pubclic class RequestProcessor {
    private static final Logger logger = Logger.getLogger(RequestProcessor.class);
    public void doSomething()  {
        ....
        final List<String> hugeList = new ArrayList<String>(10000);
        new Thread() {
           public void run() {
               logger.info("Child thread spawned")
               for(String s:hugeList) {
                   ....
               }
           }
        }.start();
    }
}    

因此,NDC上下文与派生的内联线程相关联。这个NDC上下文的关键线程对象是一个内联线程,它挂着hugeList对象。因此,即使线程完成了它正在做的事情,对hugeList的引用也会被NDC上下文Hastable保持活动状态,从而导致内存泄漏。

其他回答

就像这样!

public static void main(String[] args) {
    List<Object> objects = new ArrayList<>();
    while(true) {
        objects.add(new Object());
    }
}

创建一个静态映射并不断添加硬引用。这些永远不会被垃圾收集。

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); }
}

您可以使用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();
        }
    }
}

要做的一件简单的事情是使用带有不正确(或不存在)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.

保存对象引用的静态字段(尤其是最终字段)

class MemorableClass {
    static final ArrayList list = new ArrayList(100);
}

(未关闭)开放流(文件、网络等)

try {
    BufferedReader br = new BufferedReader(new FileReader(inputFile));
    ...
    ...
} catch (Exception e) {
    e.printStackTrace();
}

未封闭的连接

try {
    Connection conn = ConnectionFactory.getConnection();
    ...
    ...
} catch (Exception e) {
    e.printStackTrace();
}

JVM垃圾收集器无法访问的区域,例如通过本机方法分配的内存。

在web应用程序中,某些对象存储在应用程序范围中,直到应用程序被显式停止或删除。

getServletContext().setAttribute("SOME_MAP", map);

不正确或不适当的JVM选项,例如IBM JDK上的noclassgc选项,它阻止未使用的类垃圾收集

请参阅IBM JDK设置。