我将通过一个完整的案例来展示Java内存泄漏的排查过程,包括问题发现、分析、定位和解决。

内存泄漏排查实战案例
案例背景
一个电商系统的订单处理服务,运行一段时间后出现响应变慢,最终导致OOM(OutOfMemory)。
问题发现
# 1. 发现应用告警 系统告警:应用响应时间超过5秒 内存使用率持续升高 最终抛出 OutOfMemoryError # 2. 查看错误日志 Exception in thread "http-nio-8080-exec-47" java.lang.OutOfMemoryError: Java heap space
初步诊断
通过监控平台查看内存趋势
# 使用jstat查看GC情况 jstat -gcutil <pid> 1000 10 # 输出结果分析 S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 0.00 0.00 92.50 98.30 95.20 90.10 1452 12.345 567 78.901 91.246 # 观察:老年代(O)使用率98.3%,Full GC频繁(567次)
获取堆dump文件
# 方法1:使用jmap生成dump文件 jmap -dump:live,format=b,file=/tmp/heap.bin <pid> # 方法2:通过JVM参数自动生成(推荐) # 在启动参数中加入: -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof
分析内存泄漏
使用MAT(Memory Analyzer Tool)分析
# 打开MAT工具,加载heap.bin文件 # 执行"Leak Suspects"报告分析
MAT分析结果:
Problem Suspect 1:
"org.apache.kafka.clients.producer.KafkaProducer"
loaded by "java.net.URLClassLoader" occupies 356,234,568 (73.4%) bytes
Key Stacktrace:
- java.util.HashMap.putMapEntries
- java.util.HashSet.add
- org.apache.kafka.common.metrics.stats.Sample
深入分析堆栈
// 发现疑似泄漏代码
@Component
public class KafkaSendService {
// 问题:静态Map保存所有发送记录
private static final Map<String, MessageInfo> MESSAGE_CACHE
= new HashMap<>(); // 无大小限制的缓存
public void sendMessage(String orderId, MessageInfo message) {
// 发送消息
kafkaTemplate.send("order-topic", message);
// 问题代码:每次发送都保存到缓存,永远不清理
MESSAGE_CACHE.put(orderId, message);
}
}
定位根因
使用jhat分析
jhat -port 7000 /tmp/heap.bin # 打开浏览器访问 http://localhost:7000 # 查看shallow heap和retained heap最大的对象
使用JVisualVM分析
// 找到泄漏的集合,查看其元素类型 HashMap<String, MessageInfo> MESSAGE_CACHE - Key: 订单号 String - Value: MessageInfo (包含大对象如byte[]) // 查看引用链 MessageInfo -> byte[] payload
代码级排查
// 完整的问题代码示例
@Service
public class OrderService {
// 泄漏源1:无限增长的静态Map
private static final Map<String, Order> ORDER_CACHE = new HashMap<>();
// 泄漏源2:ThreadLocal使用不当
private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT =
new ThreadLocal<>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
// 泄漏源3:监听器未注册
private static final List<OrderListener> LISTENERS = new ArrayList<>();
public void processOrder(Order order) {
// 1. 缓存订单,但从未移除(内存泄漏)
ORDER_CACHE.put(order.getId(), order);
// 2. ThreadLocal使用后未remove
DATE_FORMAT.get().format(new Date());
// 应该在finally中调用DATE_FORMAT.remove()
// 3. 添加监听器,但从未移除
LISTENERS.add(new OrderListener() {
@Override
public void onOrderProcessed(Order o) {
// 处理逻辑
}
});
// 4. 大对象未及时置null
byte[] bigData = new byte[10 * 1024 * 1024];
// 处理...
// bigData应置null
}
}
解决方案
修复代码
@Service
public class OrderService {
// 修复1:使用Guava Cache并设置过期策略
private final Cache<String, Order> ORDER_CACHE =
CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(30, TimeUnit.MINUTES)
.build();
// 修复2:使用安全的DateFormat
private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
public void processOrder(Order order) {
// 修复1:使用cache
ORDER_CACHE.put(order.getId(), order);
// 修复2:正确使用ThreadLocal
try {
DATE_FORMAT.get().format(new Date());
} finally {
DATE_FORMAT.remove(); // 关键:使用后必须remove
}
// 修复3:使用WeakReference
List<OrderListener> listeners = new CopyOnWriteArrayList<>();
// 或者定期清理不需要的监听器
}
}
优化JVM参数
# 调整堆内存设置 JAVA_OPTS="-Xms2g -Xmx2g -XX:+UseG1GC \ -XX:MaxGCPauseMillis=100 \ -XX:+HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath=/logs/heapdump.hprof \ -XX:+PrintGCDetails -XX:+PrintGCDateStamps \ -Xloggc:/logs/gc.log"
预防措施
增加监控和告警
@Component
public class MemoryMonitor {
@Scheduled(fixedDelay = 60000)
public void monitorMemory() {
MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
double usedMemory = (double) heapUsage.getUsed() / heapUsage.getMax();
if (usedMemory > 0.8) {
// 发送告警
log.error("Memory usage high: {}%", usedMemory * 100);
// 自动触发dump
dumpHeap();
}
}
private void dumpHeap() {
try {
String dumpPath = "/logs/memory-dump-" +
System.currentTimeMillis() + ".hprof";
ManagementFactory.getPlatformMBeanServer()
.invoke(new ObjectName("com.sun.management:type=HotSpotDiagnostic"),
"dumpHeap",
new Object[]{dumpPath, true},
new String[]{"java.lang.String", "boolean"});
} catch (Exception e) {
log.error("Dump heap failed", e);
}
}
}
内存泄漏检测工具配置
// 使用LeakCanary(Android)或similar工具 // 配置定期清理和检查 // 使用JFR(Java Flight Recorder)监控 -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:StartFlightRecording=duration=60s,filename=recording.jfr
验证解决方案
# 1. 修复后运行24小时,检查内存趋势 jstat -gcutil <pid> 5000 # 观察输出 S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 0.00 0.00 45.20 52.30 78.20 70.10 2452 20.345 12 5.901 26.246 # 老年代使用率稳定在50%左右,Full GC次数显著下降 # 2. 使用JDK自带的工具验证 jcmd <pid> GC.class_histogram | head -20
总结排查经验
排查步骤:
- 发现问题 → 监控告警/OOM
- 初步诊断 → jstat查看GC情况
- 获取分析数据 → 生成heap dump
- 分析定位 → MAT/jhat分析泄漏点
- 代码审查 → 检查静态集合、缓存、ThreadLocal等
- 修复验证 → 代码修复并持续监控
常见泄漏模式:
- 静态集合类无限增长
- 未关闭的资源(JDBC、IO流)
- ThreadLocal未清理
- 监听器未注销
- 缓存无过期策略
- 内部类持有外部类引用
这个案例展示了完整的内存泄漏排查流程,实际工作中需要结合具体场景灵活运用这些工具和方法。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播