如何在Java中创建内存泄漏?
如何在Java中创建内存泄漏?
我刚刚参加了一次面试,被要求使用Java创建一个内存泄漏。
不用说,我感觉非常愚蠢,完全不知道如何开始创建一个内存泄漏。
你能给出一个例子吗?
admin 更改状态以发布 2023年5月25日
持有对象引用的静态字段[尤其是一个 final 字段]
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设置。
这是在纯Java中创建一个真正的内存泄漏(对象虽然无法通过运行代码访问但仍存储在内存中)的好方法:
- 应用程序创建一个长时间运行的线程(或使用线程池以更快速地泄漏)。
- 线程通过(可选的自定义)
ClassLoader
加载一个类。 - 该类分配一大块内存(例如
new byte[1000000]
),在一个静态字段中存储对它的强引用,然后在ThreadLocal
中存储对自身的引用。分配额外的内存是可选的(泄漏类实例已足够),但它将使泄漏工作得更快。 - 应用程序清除对自定义类或其加载的
ClassLoader
的所有引用。 - 重复执行。
由于在Oracle的JDK中实现ThreadLocal
的方式,这会创建一个内存泄漏:
- 每个
Thread
都有一个私有字段threadLocals
,实际上存储线程局部值。 - 此映射中的每个键都是对
ThreadLocal
对象的弱引用,因此在垃圾收集器收集ThreadLocal
对象后,其条目将从映射中移除。 - 但每个值都是一个强引用,因此当一个值(直接或间接地)指向作为其键的
ThreadLocal
对象时,只要线程还活着,该对象就不会被垃圾收集,也不会从映射中移除。
在这个例子中,强引用链如下所示:
Thread
对象 → threadLocals
映射 → 示例类的实例 → 示例类 → 静态ThreadLocal
字段 → ThreadLocal
对象。
(ClassLoader
实际上并不在创建内存泄漏中发挥作用,它只是使内存泄漏更加严重,因为它增加了一个附加引用链:示例类 → ClassLoader
→ 它加载的所有类。在许多JVM实现中,特别是在Java 7之前,情况甚至更糟,因为类和ClassLoader
直接分配到永久代中,根本不会被垃圾收集。)
这种模式的一个变种是,如果你经常重新部署应用程序,并且这些应用程序恰好使用了某些方式指向自身的ThreadLocal
,那么应用程序容器(如Tomcat)就会像漏斗一样泄漏内存。这可能会因为许多微妙的原因发生,并且通常很难调试或修复。
更新:由于许多人一直在要求,这里提供一些演示此行为的示例代码。