本文目录导读:

Java堆转储分析实战案例
案例背景
某电商系统在促销活动期间出现频繁Full GC和内存溢出(OOM)问题,用户反映系统响应缓慢,部分请求超时,需要分析堆转储文件定位内存泄漏问题。
问题排查过程
环境信息
# 系统参数 Java版本: JDK 1.8.0_281 JVM参数: -Xms4g -Xmx4g -XX:+UseG1GC 堆转储文件: heap_dump_20240115.hprof (5.2GB)
获取堆转储
# 方法1: 自动生成(配置OOM时自动dump) -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/dumps/ # 方法2: 手动获取 jmap -dump:format=b,file=heap_dump_20240115.hprof <PID>
使用MAT分析堆转储
打开堆转储
# 使用Eclipse MAT打开 mat heap_dump_20240115.hprof
查看概览信息
// 堆大小概览
Total heap size: 4.0GB
Used heap: 3.8GB (95%)
Objects: 12,543,210
Classes: 28,456
// 关键结论
占用大量内存的对象类型前5名:
1. Order对象: 2.1GB
2. User对象: 800MB
3. String对象: 450MB
4. HashMap$Node: 300MB
5. Product对象: 250MB
使用Dominator Tree分析
// 分析结果
Shallow Heap | Retained Heap | Percentage
--------------------------------------------------
ConcurrentHashMap | 1.8GB | 45%
└─ Order[] | 1.5GB | 37%
└─ Order | 1.2GB | 30%
└─ List<Product> | 800MB | 20%
--------------------------------------------------
ThreadLocalMap | 600MB | 15%
└─ UserContext | 500MB | 12%
查看可疑对象(Leak Suspects)
// MAT自动检测到的嫌疑对象
Problem Suspect 1:
"订单缓存对象"
占用了堆内存的45%
被67.3%的对象引用
持有者: OrderCacheService
Problem Suspect 2:
"用户会话信息"
占用了堆内存的15%
被ThreadLocal持有
深入分析定位
分析Order对象堆积原因
// 通过OQL查询订单对象 SELECT * FROM com.shop.model.Order o WHERE o.status == "PENDING" // 查询结果 PENDING状态订单: 1,250,000个 占订单总量的78% 创建时间最早: 2024-01-10 说明这些订单从未被处理
分析引用链
// 通过GC Roots追踪
GC Roots →
Thread →
OrderCacheService →
ConcurrentHashMap<String, List<Order>>
// 发现静态缓存持有所有订单
private static Map<String, List<Order>> orderCache = new ConcurrentHashMap<>();
// 业务逻辑问题
public void cacheOrders(String userId, List<Order> orders) {
// 问题:没有清理机制
orderCache.computeIfAbsent(userId, k -> new ArrayList<>())
.addAll(orders);
}
分析UserContext问题
// ThreadLocal使用问题
public class UserContext {
// 静态ThreadLocal持有用户信息
private static ThreadLocal<User> currentUser = new ThreadLocal<>();
// 问题:线程池中线程未清理
public void processOrder(Order order) {
User user = currentUser.get(); // 返回旧用户信息
// 业务处理
}
}
优化方案
订单缓存优化
@Service
public class OrderCacheService {
// 使用带过期时间的缓存
private final Cache<String, List<Order>> orderCache;
public OrderCacheService() {
// Guava Cache,设置过期时间
this.orderCache = CacheBuilder.newBuilder()
.maximumSize(10000) // 最大缓存数量
.expireAfterWrite(30, TimeUnit.MINUTES) // 过期时间
.build();
}
// 添加清理机制
@Scheduled(cron = "0 */15 * * * ?")
public void cleanExpiredOrders() {
orderCache.cleanUp();
}
}
使用WeakReference优化
// 用户上下文使用WeakReference
public class UserContext {
private static final ThreadLocal<WeakReference<User>> CURRENT_USER =
new ThreadLocal<>();
public static void setUser(User user) {
CURRENT_USER.set(new WeakReference<>(user));
}
public static User getUser() {
WeakReference<User> ref = CURRENT_USER.get();
return ref != null ? ref.get() : null;
}
// 必须清理
public static void clear() {
CURRENT_USER.remove();
}
}
线程池TaskDecorator清理ThreadLocal
@Configuration
public class ThreadPoolConfig {
@Bean(name = "asyncExecutor")
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(100);
executor.setWaitForTasksToCompleteOnShutdown(true);
// 使用TaskDecorator清理ThreadLocal
executor.setTaskDecorator(runnable -> {
return () -> {
try {
runnable.run();
} finally {
UserContext.clear();
}
};
});
return executor;
}
}
优化效果验证
对比分析
# 优化前 堆内存使用率: 95% Full GC频率: 每30秒1次 平均GC时间: 2.5秒 请求超时率: 8% # 优化后 堆内存使用率: 45% Full GC频率: 每10分钟1次 平均GC时间: 200ms 请求超时率: 0.01%
关键指标
// 使用JMX获取监控指标
public class MemoryMonitor {
public static void monitorMemory() {
MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
MemoryUsage heapUsage = memoryMXBean.getHeapMemoryUsage();
System.out.println("已用内存: " + heapUsage.getUsed() / 1024 / 1024 + "MB");
System.out.println("最大内存: " + heapUsage.getMax() / 1024 / 1024 + "MB");
// 检查内存泄漏
if (heapUsage.getUsed() / (double) heapUsage.getMax() > 0.8) {
System.out.println("警告: 内存使用率超过80%");
}
}
}
预防措施
内存监控预警
# Prometheus监控配置 - metric: jvm_memory_used_bytes threshold: 80% action: "触发告警事件"
代码审查清单
// 检查项1: 静态集合是否持有大量数据
// 检查项2: ThreadLocal是否正确清理
// 检查项3: 缓存是否设置过期时间
// 检查项4: 大对象是否及时处理
public class MemoryCheckUtil {
// 定期执行内存检查
public static void checkMemoryLeak() {
List<Object> largeObjects = new ArrayList<>();
// 检查超过100MB的对象
GC.visitObjects((obj) -> {
if (getObjectSize(obj) > 100 * 1024 * 1024) {
largeObjects.add(obj);
System.out.println("发现大对象: " + obj.getClass().getName());
}
});
}
}
自动化测试
// 编写内存泄漏测试用例
@Test
public void testMemoryLeak() {
OrderCacheService cacheService = new OrderCacheService();
// 模拟大量订单
for (int i = 0; i < 100000; i++) {
Order order = new Order();
order.setId(String.valueOf(i));
cacheService.cacheOrders("user" + i, Arrays.asList(order));
}
// 验证缓存清理机制
cacheService.cleanExpiredOrders();
assertEquals(0, cacheService.getCacheSize());
}
通过堆转储分析,我们成功定位并解决了:
- 订单缓存无界增长 - 导致堆内存耗尽的主要原因
- ThreadLocal线程未清理 - 导致内存泄漏的次要原因
- 缓存策略不当 - 缺少过期和大小限制机制
本次优化不仅解决了OOM问题,还显著提升了系统性能,通过建立监控、预防和测试机制,保证了系统的长期稳定运行。