Java 虽然有自动垃圾回收(GC),不少开发者就觉得内存泄漏这事儿跟自己没啥关系。可别想得太美,内存泄漏在 Java 里照样会找上门!啥叫内存泄漏?就是那些用不着了的对像还被不小心拽着引用不放,GC 想收也收不走。
Java 内存泄漏的常见元凶
1. 静态集合(Map、List 之类)
扔进静态集合里的东西没人管着不删。2. 没关的资源(文件、数据库连接、流)
3. 没移除的监听器和回调(比如 Swing、Android 里的事件监听)
4. ThreadLocal 变量没清理
5. 类加载器泄漏(网页应用重部署时常见)
真实案例:缓存系统里的内存泄漏
场景
一个 Java 应用用静态 HashMap 缓存用户会话。时间一长,用不着的会话还占着内存,最后崩出 OutOfMemoryError。
问题代码(会漏的写法)
import java.util.HashMap;
import java.util.Map;
public class SessionCache {
private static final Map<Long, UserSession> cache = new HashMap<>();
public void addUserSession(Long userId, UserSession session) {
cache.put(userId, session); // 不手动删就永远留着!
}
public UserSession getSession(Long userId) {
return cache.get(userId);
}
// 缺了个清理无用会话的方法!
}
为啥会漏?
• 静态 Map 跟应用活得一样长。
• UserSession 用不着了还赖在 cache 里不走。
• 时间一久,堆内存塞满,OutOfMemoryError 就来了。
解决办法 1:用 WeakHashMap(自动清理)
import java.util.Map;
import java.util.WeakHashMap;
public class SessionCache {
// WeakHashMap 让 GC 能自动干掉没用的条目
private static final Map<Long, UserSession> cache = new WeakHashMap<>();
public void addUserSession(Long userId, UserSession session) {
cache.put(userId, session);
}
public UserSession getSession(Long userId) {
return cache.get(userId);
}
}
为啥管用?
WeakHashMap 用的是弱引用,只要别的地方不拽着 UserSession,GC 就能把它扫走。
解决办法 2:手动清理(适合严格控制缓存)
import java.util.HashMap;
import java.util.Map;
public class SessionCache {
private static final Map<Long, UserSession> cache = new HashMap<>();
public void addUserSession(Long userId, UserSession session) {
cache.put(userId, session);
}
public UserSession getSession(Long userId) {
return cache.get(userId);
}
// 用不着时手动删会话
public void removeSession(Long userId) {
cache.remove(userId);
}
}
啥时候用这个?
• 想自己掌控啥时候清缓存。
• 用户退出或不活跃时,调用 removeSession()。
另一个实战例子:没关的资源
问题(文件泄漏)
public void readFile(String path) {
try {
BufferedReader reader = new BufferedReader(new FileReader(path));
String line = reader.readLine(); // 出异常咋办?
// ...处理文件...
// 缺了 reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
为啥会漏?
• 出异常时 reader 没关,文件句柄就漏了。
修复:用 Try-With-Resources(自动关闭)
public void readFile(String path) {
try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
String line = reader.readLine();
// ...处理文件...
} catch (IOException e) {
e.printStackTrace();
}
}
为啥好使?
try-with-resources 保证就算出异常也能调用 close()。
咋找出 Java 内存泄漏?
• VisualVM / Java Mission Control:盯着堆内存用量。
• Eclipse Memory Analyzer (MAT):分析堆转储。
• -Xmx & -Xms 参数:调小堆大小,早点暴露泄漏。
• 性能分析工具:YourKit、JProfiler。
避免 Java 内存泄漏的绝招
• 少用长寿的静态集合(或者上 WeakHashMap)。
• 资源(连接、流、文件)一定记得关。
• 监听器/回调用完就删(比如 Swing 的 ActionListener)。
• ThreadLocal 用完调用 remove()。
• 生产环境盯着堆内存用量。