为什么Java代码会泄漏?在2025年创建并捕获内存泄漏

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

引言:“破坏比建设容易”

\n

每位Java开发人员都曾在生产环境中诅咒过内存泄漏(memory leak)。应用程序因OutOfMemoryError崩溃,服务器重启,用户发送愤怒的邮件。但硬币也有另一面:如何在Java中故意创建内存泄漏?听起来很疯狂,但正是这个问题(源自Stack Overflow)获得了数千次浏览。

\n

为什么要了解这个?为了理解垃圾回收器(GC)的机制以及那些让你的代码千疮百孔的反模式。在2025年,当Java 21及其虚拟线程和Loom已成为标准时,旧的内存泄漏并未消失——它们只是伪装成了新特性。

\n

在本文中,我们不仅会展示如何“搞砸”一个Java应用程序,还会提供2025年检测内存泄漏的最佳实践。准备好了吗?开始吧!

\n\n

一点理论:为什么Java并非“永恒”

\n

许多新手坚信垃圾回收器(GC)会自动删除所有不需要的东西。现实是残酷的:Java中的内存泄漏是可能的,如果一个对象从根引用(GC roots)保持可达(reachable),但应用程序却不再需要它。

\n

在2025年,随着Value Objects(Project Valhalla)和改进的ZGC(暂停时间小于1毫秒的垃圾回收器)的出现,性能状况有所改善,但内存泄漏的逻辑保持不变。GC不是读心术——它不知道你忘记在HashMap中清空引用。

\n\n

方法一:经典——“健忘的”HashMap

\n

这是最流行的反模式,在文章中反复出现。但不得不提。

\n

泄漏如何发生

\n

你将对象作为键放入HashMap(或HashSet),但忘记删除它们。如果键是可变的(mutable)对象,在插入后改变了哈希码,那么你肯定会遇到Java内存泄漏。

\n\n
import java.util.HashMap;\nimport java.util.Map;\n\npublic class LeakyMapExample {\n    public static void main(String[] args) {\n        Map<StringBuilder, String> map = new HashMap<>();\n\n        for (int i = 0; i < 100_000; i++) {\n            StringBuilder key = new StringBuilder("key_" + i);\n            map.put(key, "value_" + i);\n            // “忘记”删除——键永远存在\n        }\n        System.out.println("映射大小: " + map.size());\n        // 泄漏:堆中有100_000个StringBuilder和String对象\n    }\n}\n
\n\n

为什么这是泄漏?只要对map的引用存在,所有键和值都会保留在堆中。即使应用程序逻辑不再使用这些数据,GC也不会触碰它们。

\n\n

2025年最佳实践

\n
    \n
  • 如果键是临时对象,请使用WeakHashMap。
  • \n
  • 对于缓存,使用带大小/时间限制的Caffeine或Guava Cache。
  • \n
  • 切勿将StringBuilder用作键——仅使用不可变(immutable)类型。
  • \n
\n\n

方法二:静态集合——沉默的杀手

\n

静态字段在JVM运行期间一直存在。如果你将数据放入static List或static Map而不清理,你会在类级别遇到Java内存泄漏。

\n\n
import java.util.ArrayList;\nimport java.util.List;\n\npublic class StaticLeak {\n    private static final List<byte[]> CACHE = new ArrayList<>();\n\n    public static void addData() {\n        // 每次调用向静态列表添加10 MB\n        CACHE.add(new byte[10 * 1024 * 1024]);\n    }\n\n    public static void main(String[] args) throws Exception {\n        for (int i = 0; i < 1_000; i++) {\n            addData();\n            Thread.sleep(100);\n        }\n        // OutOfMemoryError不可避免\n    }\n}\n
\n\n

<

博客

书籍推荐