网站首页 > java教程 正文
某中台服务因内存泄漏导致每天凌晨Full GC,服务不可用超30分钟!本文通过MAT分析+线上排查,揭示静态集合、线程局部、第三方库的泄漏模式,提供根治方案。
一、静态集合:最常见的内存泄漏源
泄漏模式分析:
// 典型泄漏代码:静态Map无限增长
public class UserCache {
private static final Map<Long, User> CACHE = new HashMap<>();
public static void addUser(User user) {
CACHE.put(user.getId(), user); // 永不删除
}
// 没有remove方法,对象永远无法回收
}
内存增长数据:
时间点 | 缓存大小 | 堆内存占用 | Full GC间隔 |
服务启动 | 0 | 500MB | 无 |
运行1天 | 50,000 | 2GB | 12小时 |
运行7天 | 350,000 | 8GB | 2小时 |
解决方案:
// 1. 使用WeakHashMap自动清理
private static final Map<Long, WeakReference<User>> CACHE = new WeakHashMap<>();
// 2. 添加容量限制和过期策略
private static final Map<Long, User> SAFE_CACHE = new LinkedHashMap<>() {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > 10000; // 最大容量限制
}
};
// 3. 使用Caffeine等专业缓存库
LoadingCache<Long, User> cache = Caffeine.newBuilder()
.maximumSize(10000)
.expireAfterWrite(1, TimeUnit.HOURS)
.build(key -> loadUserFromDB(key));
二、ThreadLocal的隐藏陷阱
泄漏场景重现:
public class UserContextHolder {
private static final ThreadLocal<User> currentUser = new ThreadLocal<>();
public static void setUser(User user) {
currentUser.set(user);
}
// 忘记调用remove()!
}
// 线程池复用场景下,ThreadLocal会一直持有对象引用
泄漏原理分析:
Thread → ThreadLocalMap → Entry → value
当线程复用且未清理时,value永远无法回收
正确使用模式:
// 方案1:使用try-finally确保清理
public void processRequest(User user) {
UserContextHolder.setUser(user);
try {
// 业务处理
businessService.process();
} finally {
UserContextHolder.clear(); // 必须清理!
}
}
// 方案2:使用JDK19+的ScopedValue
private static final ScopedValue<User> CURRENT_USER = ScopedValue.newInstance();
public void processRequest(User user) {
ScopedValue.where(CURRENT_USER, user).run(() -> {
// 作用域结束自动清理
businessService.process();
});
}
三、第三方库泄漏排查技巧
常见泄漏源识别:
// 1. 数据库连接池未关闭
Connection conn = dataSource.getConnection();
// 忘记conn.close()
// 2. 文件流未关闭
FileInputStream fis = new FileInputStream("large.file");
// 忘记fis.close()
// 3. 网络连接未释放
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 忘记conn.disconnect()
排查工具链:
# 1. 生成堆转储文件
jmap -dump:live,format=b,file=heap.hprof <pid>
# 2. 实时监控内存变化
jstat -gc <pid> 5s
# 3. 查看对象直方图
jmap -histo:live <pid> | head -20
四、生产环境排查实战
MAT分析步骤:
// 1. 查找支配树中的大对象
// 在Eclipse MAT中执行:
// Path To GC Roots → exclude weak/soft references
// 2. 分析重复字符串
// Open Dominator Tree → Group by class → java.lang.String
// 3. 检查线程栈
// Open Thread Overview → 查看线程局部变量
自动化泄漏检测:
@Component
public class MemoryLeakDetector {
private static final long MEMORY_THRESHOLD = 1024 * 1024 * 500; // 500MB
@Scheduled(fixedRate = 60000) // 每分钟检查一次
public void checkMemoryLeak() {
Runtime runtime = Runtime.getRuntime();
long usedMemory = runtime.totalMemory() - runtime.freeMemory();
if (usedMemory > MEMORY_THRESHOLD) {
// 触发堆转储
try {
String dumpFile = "heap-dump-" + System.currentTimeMillis() + ".hprof";
HotSpotDiagnosticMXBean bean = ManagementFactory.getPlatformMXBean(
HotSpotDiagnosticMXBean.class);
bean.dumpHeap(dumpFile, true);
alertService.send("内存使用超阈值,已生成堆转储: " + dumpFile);
} catch (IOException e) {
log.error("生成堆转储失败", e);
}
}
}
}
五、根治方案与最佳实践
代码审查清单:
## 内存泄漏防护清单
### 集合使用
- [ ] 静态集合是否有容量限制?
- [ ] 缓存是否设置过期时间?
- [ ] 是否使用弱引用/软引用?
### 资源管理
- [ ] 所有Closeable资源是否使用try-with-resources?
- [ ] ThreadLocal是否在finally块中清理?
- [ ] 连接池连接是否正确关闭?
### 第三方库
- [ ] 是否了解所用框架的内存管理机制?
- [ ] 是否有内存泄漏的历史issue?
JVM参数优化:
# 内存泄漏检测专用参数
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/path/to/dumps
-XX:NativeMemoryTracking=detail
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:/path/to/gc.log
监控体系建设:
# Prometheus监控配置
- name: jvm_memory_used
rules:
- alert: MemoryLeakDetected
expr: increase(jvm_memory_used_bytes{area="heap"}[1h]) > 500000000
for: 10m
labels:
severity: warning
annotations:
summary: "疑似内存泄漏,堆内存持续增长"
猜你喜欢
- 2025-10-14 看完这篇文,别再说你不懂Java内存模型了!
- 2025-10-14 Java volatile关键字深度解析:多线程编程的"内存屏障"神器
- 2025-10-14 Java内存模型JMM重要知识点_java内存模型有哪些
- 2025-10-14 Java 内存模型与并发编程中的可见性、原子性、有序性有啥关联
- 2025-10-14 让我们深入了解有关Java内存泄漏的10件事情
- 2025-10-14 Java中的volatile与操作系统的内存重排详解
- 2025-10-14 Java内存模型的历史变迁_java内存模型原理
- 2025-10-14 Kubernetes 下 Java 应用内存调优实战指南
- 2025-10-14 java使用NMT Native Memory Tracking分析内存占用
- 2025-10-14 【java面试100问】03 在生产环境上,发现内存泄漏问题,如何排查?
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)