本章概述
内存分析是 Java 应用性能调优的核心环节之一。本章将深入探讨如何使用 VisualVM 进行内存分析,包括堆内存监控、堆转储生成与分析、内存泄漏检测以及内存优化策略。通过本章的学习,你将掌握识别和解决内存相关问题的专业技能。
学习目标
- 理解 Java 内存模型和堆内存结构
- 掌握 VisualVM 内存监控功能的使用
- 学会生成和分析堆转储文件
- 能够检测和诊断内存泄漏问题
- 掌握内存优化的最佳实践
1. Java 内存模型基础
1.1 JVM 内存结构
┌─────────────────────────────────────────────────────────────┐
│ JVM 内存结构 │
├─────────────────────────────────────────────────────────────┤
│ 堆内存 (Heap Memory) │
│ ┌─────────────────┬─────────────────────────────────────┐ │
│ │ 年轻代 (Young) │ 老年代 (Old/Tenured) │ │
│ │ ┌─────┬────────┤ │ │
│ │ │Eden │Survivor│ │ │
│ │ │ │ S0│S1 │ │ │
│ │ └─────┴────────┤ │ │
│ └─────────────────┴─────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 非堆内存 (Non-Heap Memory) │
│ ┌─────────────────┬─────────────────┬─────────────────┐ │
│ │ 方法区 │ 代码缓存 │ 压缩类空间 │ │
│ │ (Method Area) │ (Code Cache) │(Compressed Class)│ │
│ └─────────────────┴─────────────────┴─────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 直接内存 (Direct Memory) │
└─────────────────────────────────────────────────────────────┘
1.2 内存区域详解
1.2.1 堆内存 (Heap Memory)
堆内存是 Java 对象实例存储的主要区域:
// 内存区域示例代码
public class MemoryRegionsExample {
// 静态变量存储在方法区
private static final String STATIC_STRING = "Static String";
private static List<Object> staticList = new ArrayList<>();
// 实例变量存储在堆内存中
private String instanceString;
private List<Object> instanceList;
public MemoryRegionsExample(String str) {
// 对象实例存储在堆内存
this.instanceString = str; // 字符串对象在堆中
this.instanceList = new ArrayList<>(); // ArrayList 对象在堆中
}
public void demonstrateMemoryAllocation() {
// 局部变量引用存储在栈中,对象实例存储在堆中
String localString = "Local String"; // 字符串常量池
StringBuilder sb = new StringBuilder(); // StringBuilder 对象在堆中
// 基本类型局部变量存储在栈中
int localInt = 42;
long localLong = 123456789L;
// 数组对象存储在堆中
int[] localArray = new int[1000];
Object[] objectArray = new Object[100];
// 循环创建对象(年轻代分配)
for (int i = 0; i < 1000; i++) {
// 短生命周期对象,通常在年轻代分配和回收
String tempString = "Temp " + i;
Object tempObject = new Object();
// 添加到集合中的对象可能晋升到老年代
if (i % 10 == 0) {
instanceList.add(tempObject);
}
}
}
public static void demonstrateStaticMemory() {
// 静态变量操作,影响方法区内存
for (int i = 0; i < 100; i++) {
staticList.add(new Object());
}
}
public static void main(String[] args) {
System.out.println("=== Memory Regions Demonstration ===");
// 创建实例(堆内存分配)
MemoryRegionsExample example = new MemoryRegionsExample("Example Instance");
// 演示内存分配
example.demonstrateMemoryAllocation();
// 演示静态内存使用
demonstrateStaticMemory();
// 打印内存使用情况
printMemoryUsage();
}
private static void printMemoryUsage() {
Runtime runtime = Runtime.getRuntime();
long totalMemory = runtime.totalMemory();
long freeMemory = runtime.freeMemory();
long usedMemory = totalMemory - freeMemory;
long maxMemory = runtime.maxMemory();
System.out.println("\n=== Memory Usage ===");
System.out.printf("Used Memory: %.2f MB%n", usedMemory / 1024.0 / 1024.0);
System.out.printf("Free Memory: %.2f MB%n", freeMemory / 1024.0 / 1024.0);
System.out.printf("Total Memory: %.2f MB%n", totalMemory / 1024.0 / 1024.0);
System.out.printf("Max Memory: %.2f MB%n", maxMemory / 1024.0 / 1024.0);
System.out.printf("Memory Usage: %.2f%%%n",
(double) usedMemory / maxMemory * 100);
}
}
1.2.2 垃圾回收机制
// 垃圾回收演示
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import java.util.*;
public class GarbageCollectionDemo {
private static final List<Object> longLivedObjects = new ArrayList<>();
private static volatile boolean running = true;
public static void main(String[] args) throws InterruptedException {
System.out.println("=== Garbage Collection Demonstration ===");
System.out.println("观察不同类型的对象分配和垃圾回收行为");
// 启动内存监控线程
Thread monitorThread = new Thread(GarbageCollectionDemo::monitorMemory);
monitorThread.setDaemon(true);
monitorThread.start();
// 启动 GC 统计线程
Thread gcStatsThread = new Thread(GarbageCollectionDemo::monitorGC);
gcStatsThread.setDaemon(true);
gcStatsThread.start();
// 场景1:大量短生命周期对象(触发年轻代 GC)
System.out.println("\n场景1:创建大量短生命周期对象...");
createShortLivedObjects();
Thread.sleep(2000);
// 场景2:长生命周期对象(可能晋升到老年代)
System.out.println("\n场景2:创建长生命周期对象...");
createLongLivedObjects();
Thread.sleep(2000);
// 场景3:大对象分配(可能直接进入老年代)
System.out.println("\n场景3:创建大对象...");
createLargeObjects();
Thread.sleep(2000);
// 场景4:内存压力测试(触发 Full GC)
System.out.println("\n场景4:内存压力测试...");
createMemoryPressure();
Thread.sleep(5000);
// 手动触发 GC
System.out.println("\n手动触发垃圾回收...");
System.gc();
Thread.sleep(2000);
running = false;
System.out.println("\n演示完成!请在 VisualVM 中观察内存使用和 GC 活动");
}
private static void createShortLivedObjects() {
for (int i = 0; i < 100000; i++) {
// 创建短生命周期对象
String temp = "Short lived object " + i;
List<Integer> tempList = new ArrayList<>();
for (int j = 0; j < 10; j++) {
tempList.add(j);
}
// 偶尔创建一些稍大的对象
if (i % 1000 == 0) {
byte[] tempArray = new byte[1024]; // 1KB
Arrays.fill(tempArray, (byte) i);
}
}
}
private static void createLongLivedObjects() {
for (int i = 0; i < 1000; i++) {
// 创建长生命周期对象并保持引用
Map<String, Object> longLivedMap = new HashMap<>();
longLivedMap.put("id", i);
longLivedMap.put("data", new byte[1024]); // 1KB 数据
longLivedMap.put("timestamp", System.currentTimeMillis());
longLivedObjects.add(longLivedMap);
// 控制创建速度
if (i % 100 == 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
private static void createLargeObjects() {
for (int i = 0; i < 10; i++) {
// 创建大对象(可能直接分配到老年代)
byte[] largeArray = new byte[1024 * 1024]; // 1MB
Arrays.fill(largeArray, (byte) (i % 256));
// 保持一些大对象的引用
if (i % 3 == 0) {
longLivedObjects.add(largeArray);
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
private static void createMemoryPressure() {
List<Object> pressureObjects = new ArrayList<>();
try {
for (int i = 0; i < 1000; i++) {
// 创建中等大小的对象增加内存压力
byte[] pressureArray = new byte[512 * 1024]; // 512KB
Arrays.fill(pressureArray, (byte) (i % 256));
pressureObjects.add(pressureArray);
// 检查内存使用情况
if (i % 100 == 0) {
Runtime runtime = Runtime.getRuntime();
long usedMemory = runtime.totalMemory() - runtime.freeMemory();
long maxMemory = runtime.maxMemory();
double memoryUsage = (double) usedMemory / maxMemory;
System.out.printf("Memory pressure iteration %d: %.2f%% used%n",
i, memoryUsage * 100);
// 如果内存使用率过高,停止创建
if (memoryUsage > 0.8) {
System.out.println("High memory usage detected, stopping pressure test");
break;
}
}
Thread.sleep(10);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (OutOfMemoryError e) {
System.out.println("OutOfMemoryError caught during pressure test");
}
}
private static void monitorMemory() {
MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
while (running) {
try {
MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
MemoryUsage nonHeapUsage = memoryBean.getNonHeapMemoryUsage();
System.out.printf("[Memory] Heap: %d/%d MB, Non-Heap: %d/%d MB%n",
heapUsage.getUsed() / 1024 / 1024,
heapUsage.getMax() / 1024 / 1024,
nonHeapUsage.getUsed() / 1024 / 1024,
nonHeapUsage.getMax() / 1024 / 1024);
Thread.sleep(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
private static void monitorGC() {
List<GarbageCollectorMXBean> gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
Map<String, Long> lastCollectionCounts = new HashMap<>();
Map<String, Long> lastCollectionTimes = new HashMap<>();
// 初始化
for (GarbageCollectorMXBean gcBean : gcBeans) {
lastCollectionCounts.put(gcBean.getName(), gcBean.getCollectionCount());
lastCollectionTimes.put(gcBean.getName(), gcBean.getCollectionTime());
}
while (running) {
try {
Thread.sleep(3000);
for (GarbageCollectorMXBean gcBean : gcBeans) {
String gcName = gcBean.getName();
long currentCount = gcBean.getCollectionCount();
long currentTime = gcBean.getCollectionTime();
long lastCount = lastCollectionCounts.get(gcName);
long lastTime = lastCollectionTimes.get(gcName);
if (currentCount > lastCount) {
long collections = currentCount - lastCount;
long time = currentTime - lastTime;
System.out.printf("[GC] %s: %d collections, %d ms total time%n",
gcName, collections, time);
lastCollectionCounts.put(gcName, currentCount);
lastCollectionTimes.put(gcName, currentTime);
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
2. VisualVM 内存监控
2.1 Monitor 标签页内存监控
2.1.1 内存监控界面
VisualVM 的 Monitor 标签页提供了实时的内存使用情况监控:
┌─────────────────────────────────────────────────────────────┐
│ Memory 监控面板 │
├─────────────────────────────────────────────────────────────┤
│ 堆内存使用情况 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Used: 245 MB Size: 512 MB Max: 1024 MB │ │
│ │ ████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │ │
│ │ 48% 使用率 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 非堆内存使用情况 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Used: 45 MB Size: 64 MB Max: 256 MB │ │
│ │ ████████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░ │ │
│ │ 70% 使用率 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ [Perform GC] [Heap Dump] │
└─────────────────────────────────────────────────────────────┘
2.1.2 内存监控指标解读
// 内存监控指标解读示例
import java.lang.management.*;
import java.util.*;
public class MemoryMonitoringMetrics {
public static void main(String[] args) throws InterruptedException {
System.out.println("=== Memory Monitoring Metrics ===");
// 启动内存指标监控
MemoryMetricsCollector collector = new MemoryMetricsCollector();
collector.startMonitoring();
// 模拟不同的内存使用模式
simulateMemoryUsagePatterns();
collector.stopMonitoring();
collector.printSummary();
}
private static void simulateMemoryUsagePatterns() throws InterruptedException {
System.out.println("\n开始模拟不同的内存使用模式...");
// 模式1:稳定增长
System.out.println("模式1:稳定内存增长");
List<Object> steadyGrowth = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
steadyGrowth.add(new byte[1024]); // 1KB per object
if (i % 100 == 0) {
Thread.sleep(100);
}
}
Thread.sleep(2000);
// 模式2:锯齿状(分配后释放)
System.out.println("模式2:锯齿状内存使用");
for (int cycle = 0; cycle < 5; cycle++) {
List<Object> tempObjects = new ArrayList<>();
// 快速分配
for (int i = 0; i < 500; i++) {
tempObjects.add(new byte[2048]); // 2KB per object
}
Thread.sleep(1000);
// 释放引用(等待 GC)
tempObjects.clear();
System.gc();
Thread.sleep(1000);
}
// 模式3:内存泄漏模拟
System.out.println("模式3:内存泄漏模拟");
simulateMemoryLeak();
}
private static void simulateMemoryLeak() throws InterruptedException {
// 模拟内存泄漏:持续增长且不释放
List<Object> leakyList = new ArrayList<>();
for (int i = 0; i < 2000; i++) {
// 创建包含循环引用的对象
LeakyObject obj = new LeakyObject("Object " + i);
obj.setReference(obj); // 自引用
leakyList.add(obj);
if (i % 200 == 0) {
Thread.sleep(100);
System.out.println("Created " + (i + 1) + " leaky objects");
}
}
}
// 模拟内存泄漏的对象
static class LeakyObject {
private String data;
private byte[] payload;
private LeakyObject reference;
public LeakyObject(String data) {
this.data = data;
this.payload = new byte[1024]; // 1KB payload
}
public void setReference(LeakyObject reference) {
this.reference = reference;
}
}
// 内存指标收集器
static class MemoryMetricsCollector {
private final MemoryMXBean memoryBean;
private final List<GarbageCollectorMXBean> gcBeans;
private final Timer timer;
private final List<MemorySnapshot> snapshots;
private volatile boolean monitoring;
public MemoryMetricsCollector() {
this.memoryBean = ManagementFactory.getMemoryMXBean();
this.gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
this.timer = new Timer("MemoryMonitor", true);
this.snapshots = new ArrayList<>();
}
public void startMonitoring() {
monitoring = true;
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
if (monitoring) {
collectSnapshot();
}
}
}, 0, 1000); // 每秒收集一次
}
public void stopMonitoring() {
monitoring = false;
timer.cancel();
}
private void collectSnapshot() {
MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
MemoryUsage nonHeapUsage = memoryBean.getNonHeapMemoryUsage();
long totalGcCount = 0;
long totalGcTime = 0;
for (GarbageCollectorMXBean gcBean : gcBeans) {
totalGcCount += gcBean.getCollectionCount();
totalGcTime += gcBean.getCollectionTime();
}
MemorySnapshot snapshot = new MemorySnapshot(
System.currentTimeMillis(),
heapUsage.getUsed(),
heapUsage.getCommitted(),
heapUsage.getMax(),
nonHeapUsage.getUsed(),
nonHeapUsage.getCommitted(),
totalGcCount,
totalGcTime
);
snapshots.add(snapshot);
// 实时输出关键指标
double heapUsagePercent = (double) heapUsage.getUsed() / heapUsage.getMax() * 100;
if (heapUsagePercent > 80) {
System.out.printf("⚠️ High heap usage: %.1f%%%n", heapUsagePercent);
}
}
public void printSummary() {
if (snapshots.isEmpty()) {
System.out.println("No monitoring data collected");
return;
}
System.out.println("\n=== Memory Monitoring Summary ===");
MemorySnapshot first = snapshots.get(0);
MemorySnapshot last = snapshots.get(snapshots.size() - 1);
System.out.printf("Monitoring duration: %.1f seconds%n",
(last.timestamp - first.timestamp) / 1000.0);
System.out.printf("Total snapshots: %d%n", snapshots.size());
// 堆内存统计
long maxHeapUsed = snapshots.stream()
.mapToLong(s -> s.heapUsed)
.max().orElse(0);
long minHeapUsed = snapshots.stream()
.mapToLong(s -> s.heapUsed)
.min().orElse(0);
double avgHeapUsed = snapshots.stream()
.mapToLong(s -> s.heapUsed)
.average().orElse(0);
System.out.printf("\nHeap Memory Usage:%n");
System.out.printf(" Min: %.2f MB%n", minHeapUsed / 1024.0 / 1024.0);
System.out.printf(" Max: %.2f MB%n", maxHeapUsed / 1024.0 / 1024.0);
System.out.printf(" Avg: %.2f MB%n", avgHeapUsed / 1024.0 / 1024.0);
// GC 统计
long gcCountDiff = last.totalGcCount - first.totalGcCount;
long gcTimeDiff = last.totalGcTime - first.totalGcTime;
System.out.printf("\nGarbage Collection:%n");
System.out.printf(" Total collections: %d%n", gcCountDiff);
System.out.printf(" Total GC time: %d ms%n", gcTimeDiff);
if (gcCountDiff > 0) {
System.out.printf(" Average GC time: %.2f ms%n",
(double) gcTimeDiff / gcCountDiff);
}
// 内存增长趋势
long heapGrowth = last.heapUsed - first.heapUsed;
System.out.printf("\nMemory Growth:%n");
System.out.printf(" Heap growth: %.2f MB%n", heapGrowth / 1024.0 / 1024.0);
if (heapGrowth > 0) {
double growthRate = (double) heapGrowth / (last.timestamp - first.timestamp) * 1000;
System.out.printf(" Growth rate: %.2f MB/s%n", growthRate / 1024.0 / 1024.0);
}
}
}
// 内存快照数据结构
static class MemorySnapshot {
final long timestamp;
final long heapUsed;
final long heapCommitted;
final long heapMax;
final long nonHeapUsed;
final long nonHeapCommitted;
final long totalGcCount;
final long totalGcTime;
public MemorySnapshot(long timestamp, long heapUsed, long heapCommitted,
long heapMax, long nonHeapUsed, long nonHeapCommitted,
long totalGcCount, long totalGcTime) {
this.timestamp = timestamp;
this.heapUsed = heapUsed;
this.heapCommitted = heapCommitted;
this.heapMax = heapMax;
this.nonHeapUsed = nonHeapUsed;
this.nonHeapCommitted = nonHeapCommitted;
this.totalGcCount = totalGcCount;
this.totalGcTime = totalGcTime;
}
}
}
2.2 内存使用模式分析
2.2.1 正常内存使用模式
内存使用量
↑
│ ╭─╮ ╭─╮ ╭─╮
│ ╱ ╲ ╱ ╲ ╱ ╲
│ ╱ ╲ ╱ ╲ ╱ ╲
│ ╱ ╲╱ ╲╱ ╲
│ ╱ ╲
└─────────────────────────────────→ 时间
分配 GC 分配 GC 分配 GC
特征:
- 锯齿状模式
- 定期的内存回收
- 内存使用在合理范围内波动
2.2.2 内存泄漏模式
内存使用量
↑
│ ╱
│ ╱╱╱
│ ╱╱╱
│ ╱╱╱
│ ╱╱╱
│ ╱╱╱
│ ╱╱╱
│╱╱╱
└─────────────────────────────────→ 时间
特征:
- 持续上升趋势
- GC 后内存不能有效回收
- 最终可能导致 OutOfMemoryError
3. 堆转储生成与分析
3.1 堆转储生成方法
3.1.1 通过 VisualVM 生成
在 VisualVM 中生成堆转储的步骤:
- 连接到目标 Java 应用程序
- 切换到 Monitor 标签页
- 点击 “Heap Dump” 按钮
- 等待堆转储生成完成
- 自动打开堆转储分析视图
3.1.2 程序化生成堆转储
// 程序化生成堆转储
import com.sun.management.HotSpotDiagnosticMXBean;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import javax.management.MBeanServer;
public class HeapDumpGenerator {
private static final String HOTSPOT_BEAN_NAME = "com.sun.management:type=HotSpotDiagnostic";
public static void generateHeapDump(String filename, boolean live) {
try {
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
HotSpotDiagnosticMXBean hotspotBean = ManagementFactory.newPlatformMXBeanProxy(
server, HOTSPOT_BEAN_NAME, HotSpotDiagnosticMXBean.class);
System.out.println("Generating heap dump: " + filename);
long startTime = System.currentTimeMillis();
hotspotBean.dumpHeap(filename, live);
long duration = System.currentTimeMillis() - startTime;
System.out.printf("Heap dump generated successfully in %d ms%n", duration);
} catch (IOException e) {
System.err.println("Failed to generate heap dump: " + e.getMessage());
e.printStackTrace();
}
}
public static void generateTimestampedHeapDump(boolean live) {
String timestamp = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")
.format(LocalDateTime.now());
String filename = "heap_dump_" + timestamp + ".hprof";
generateHeapDump(filename, live);
}
public static void generateHeapDumpOnOutOfMemory() {
// 这个方法演示如何在程序中设置 OOM 时自动生成堆转储
// 实际应该通过 JVM 参数设置:
// -XX:+HeapDumpOnOutOfMemoryError
// -XX:HeapDumpPath=/path/to/dumps/
System.out.println("To enable automatic heap dump on OOM, use JVM parameters:");
System.out.println("-XX:+HeapDumpOnOutOfMemoryError");
System.out.println("-XX:HeapDumpPath=/path/to/dumps/");
}
public static void main(String[] args) {
System.out.println("=== Heap Dump Generator ===");
// 创建一些对象用于演示
createSampleObjects();
// 生成堆转储(包含所有对象)
System.out.println("\n生成完整堆转储(包含所有对象)...");
generateTimestampedHeapDump(false);
// 触发 GC
System.out.println("\n触发垃圾回收...");
System.gc();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 生成活动对象堆转储(仅包含存活对象)
System.out.println("\n生成活动对象堆转储(仅包含存活对象)...");
generateTimestampedHeapDump(true);
System.out.println("\n堆转储生成完成!");
System.out.println("请使用 VisualVM 或其他工具分析生成的 .hprof 文件");
}
private static void createSampleObjects() {
System.out.println("创建示例对象...");
// 创建各种类型的对象用于堆转储分析
// 1. 字符串对象
for (int i = 0; i < 1000; i++) {
String str = "Sample String " + i;
}
// 2. 集合对象
java.util.List<Object> list = new java.util.ArrayList<>();
for (int i = 0; i < 500; i++) {
list.add(new SampleObject("Object " + i, i));
}
// 3. 数组对象
int[] intArray = new int[10000];
for (int i = 0; i < intArray.length; i++) {
intArray[i] = i;
}
// 4. 自定义对象
SampleObject[] objectArray = new SampleObject[100];
for (int i = 0; i < objectArray.length; i++) {
objectArray[i] = new SampleObject("Array Object " + i, i);
}
System.out.println("示例对象创建完成");
}
// 示例对象类
static class SampleObject {
private String name;
private int value;
private byte[] data;
public SampleObject(String name, int value) {
this.name = name;
this.value = value;
this.data = new byte[1024]; // 1KB 数据
java.util.Arrays.fill(data, (byte) (value % 256));
}
@Override
public String toString() {
return String.format("SampleObject{name='%s', value=%d}", name, value);
}
}
}
3.1.3 命令行生成堆转储
# 使用 jcmd 生成堆转储
jcmd <pid> GC.run_finalization
jcmd <pid> VM.gc
jcmd <pid> GC.dump_heap /path/to/heap_dump.hprof
# 使用 jmap 生成堆转储
jmap -dump:live,format=b,file=/path/to/heap_dump.hprof <pid>
# 使用 jmap 生成所有对象的堆转储
jmap -dump:format=b,file=/path/to/heap_dump_all.hprof <pid>
3.2 堆转储分析
3.2.1 VisualVM 堆转储分析界面
┌─────────────────────────────────────────────────────────────┐
│ Heap Dump 分析界面 │
├─────────────────────────────────────────────────────────────┤
│ Summary | Classes | Instances | OQL Console │
├─────────────────────────────────────────────────────────────┤
│ Summary 标签页: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Environment: │ │
│ │ JVM: OpenJDK 64-Bit Server VM 11.0.16 │ │
│ │ OS: Windows 10 x64 │ │
│ │ Date: 2024-01-15 14:30:45 │ │
│ │ │ │
│ │ Basic Info: │ │
│ │ Total Size: 245.6 MB │ │
│ │ Total Classes: 1,234 │ │
│ │ Total Instances: 1,234,567 │ │
│ │ Total Class Loaders: 45 │ │
│ │ │ │
│ │ Largest Objects: │ │
│ │ java.lang.Object[] 45.2 MB (18.4%) │ │
│ │ java.lang.String 23.1 MB (9.4%) │ │
│ │ char[] 18.7 MB (7.6%) │ │
│ │ byte[] 15.3 MB (6.2%) │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
3.2.2 自动化堆转储分析工具
// 堆转储自动分析工具
import java.io.*;
import java.util.*;
import java.util.regex.*;
public class HeapDumpAnalyzer {
public static class HeapAnalysisResult {
public long totalSize;
public int totalClasses;
public long totalInstances;
public Map<String, ClassInfo> classInfoMap;
public List<String> suspiciousPatterns;
public Map<String, Long> largestObjects;
public HeapAnalysisResult() {
classInfoMap = new HashMap<>();
suspiciousPatterns = new ArrayList<>();
largestObjects = new LinkedHashMap<>();
}
public void printSummary() {
System.out.println("=== Heap Dump Analysis Summary ===");
System.out.printf("Total heap size: %.2f MB%n", totalSize / 1024.0 / 1024.0);
System.out.printf("Total classes: %d%n", totalClasses);
System.out.printf("Total instances: %,d%n", totalInstances);
System.out.println("\n🔍 Top classes by memory usage:");
classInfoMap.entrySet().stream()
.sorted((e1, e2) -> Long.compare(e2.getValue().totalSize, e1.getValue().totalSize))
.limit(10)
.forEach(entry -> {
ClassInfo info = entry.getValue();
double percentage = (double) info.totalSize / totalSize * 100;
System.out.printf(" %-40s %,8d instances %8.2f MB (%5.2f%%)%n",
entry.getKey(), info.instanceCount,
info.totalSize / 1024.0 / 1024.0, percentage);
});
if (!suspiciousPatterns.isEmpty()) {
System.out.println("\n⚠️ Suspicious patterns detected:");
suspiciousPatterns.forEach(pattern ->
System.out.println(" " + pattern));
}
System.out.println("\n📊 Largest individual objects:");
largestObjects.entrySet().stream()
.limit(5)
.forEach(entry ->
System.out.printf(" %-40s %8.2f MB%n",
entry.getKey(), entry.getValue() / 1024.0 / 1024.0));
}
}
public static class ClassInfo {
public long instanceCount;
public long totalSize;
public long averageSize;
public ClassInfo(long instanceCount, long totalSize) {
this.instanceCount = instanceCount;
this.totalSize = totalSize;
this.averageSize = instanceCount > 0 ? totalSize / instanceCount : 0;
}
}
// 注意:这是一个简化的分析器示例
// 实际的 .hprof 文件解析需要专门的库,如 Eclipse MAT 的 API
public static HeapAnalysisResult analyzeHeapDump(String hprofFile) {
HeapAnalysisResult result = new HeapAnalysisResult();
// 这里演示分析逻辑,实际需要使用专门的 HPROF 解析库
System.out.println("Analyzing heap dump: " + hprofFile);
System.out.println("Note: This is a simplified analyzer for demonstration");
System.out.println("For real analysis, use tools like Eclipse MAT, VisualVM, or JProfiler");
// 模拟分析结果
simulateAnalysisResults(result);
return result;
}
private static void simulateAnalysisResults(HeapAnalysisResult result) {
// 模拟堆转储分析结果
result.totalSize = 256 * 1024 * 1024; // 256 MB
result.totalClasses = 1500;
result.totalInstances = 2000000;
// 模拟类信息
result.classInfoMap.put("java.lang.String", new ClassInfo(150000, 24 * 1024 * 1024));
result.classInfoMap.put("char[]", new ClassInfo(150000, 18 * 1024 * 1024));
result.classInfoMap.put("java.lang.Object[]", new ClassInfo(5000, 45 * 1024 * 1024));
result.classInfoMap.put("byte[]", new ClassInfo(8000, 16 * 1024 * 1024));
result.classInfoMap.put("java.util.HashMap$Node", new ClassInfo(80000, 12 * 1024 * 1024));
result.classInfoMap.put("java.util.ArrayList", new ClassInfo(25000, 8 * 1024 * 1024));
result.classInfoMap.put("com.example.CustomObject", new ClassInfo(10000, 15 * 1024 * 1024));
// 检测可疑模式
detectSuspiciousPatterns(result);
// 模拟最大对象
result.largestObjects.put("Large byte array #1", 8L * 1024 * 1024);
result.largestObjects.put("Large Object array #1", 6L * 1024 * 1024);
result.largestObjects.put("Large HashMap #1", 4L * 1024 * 1024);
result.largestObjects.put("Large String array #1", 3L * 1024 * 1024);
result.largestObjects.put("Large ArrayList #1", 2L * 1024 * 1024);
}
private static void detectSuspiciousPatterns(HeapAnalysisResult result) {
// 检测可疑的内存使用模式
for (Map.Entry<String, ClassInfo> entry : result.classInfoMap.entrySet()) {
String className = entry.getKey();
ClassInfo info = entry.getValue();
// 检测大量小对象
if (info.instanceCount > 100000 && info.averageSize < 100) {
result.suspiciousPatterns.add(
String.format("Large number of small objects: %s (%,d instances, avg %.1f bytes)",
className, info.instanceCount, (double) info.averageSize));
}
// 检测内存占用过大的类
double percentage = (double) info.totalSize / result.totalSize * 100;
if (percentage > 20) {
result.suspiciousPatterns.add(
String.format("Class consuming large memory: %s (%.1f%% of total heap)",
className, percentage));
}
// 检测可能的内存泄漏
if (className.contains("Cache") || className.contains("Pool")) {
if (info.instanceCount > 10000) {
result.suspiciousPatterns.add(
String.format("Potential memory leak in cache/pool: %s (%,d instances)",
className, info.instanceCount));
}
}
}
// 检测字符串重复
ClassInfo stringInfo = result.classInfoMap.get("java.lang.String");
ClassInfo charArrayInfo = result.classInfoMap.get("char[]");
if (stringInfo != null && charArrayInfo != null) {
if (stringInfo.instanceCount > 50000) {
result.suspiciousPatterns.add(
String.format("Large number of String objects: %,d instances (consider string interning)",
stringInfo.instanceCount));
}
}
}
public static void main(String[] args) {
if (args.length != 1) {
System.out.println("Usage: java HeapDumpAnalyzer <heap_dump.hprof>");
System.out.println("\nNote: This is a demonstration analyzer.");
System.out.println("For production use, consider:");
System.out.println(" - Eclipse Memory Analyzer (MAT)");
System.out.println(" - VisualVM");
System.out.println(" - JProfiler");
System.out.println(" - JConsole");
return;
}
String hprofFile = args[0];
if (!new File(hprofFile).exists()) {
System.err.println("Heap dump file not found: " + hprofFile);
return;
}
HeapAnalysisResult result = analyzeHeapDump(hprofFile);
result.printSummary();
System.out.println("\n💡 Analysis Tips:");
System.out.println(" 1. Focus on classes with high memory usage");
System.out.println(" 2. Look for unexpected object counts");
System.out.println(" 3. Check for duplicate strings (consider interning)");
System.out.println(" 4. Investigate large arrays and collections");
System.out.println(" 5. Analyze object reference chains for memory leaks");
}
}
4. 内存泄漏检测
4.1 内存泄漏类型
4.1.1 常见内存泄漏场景
// 内存泄漏示例代码
import java.util.*;
import java.util.concurrent.*;
public class MemoryLeakExamples {
// 1. 静态集合导致的内存泄漏
private static final List<Object> STATIC_LIST = new ArrayList<>();
private static final Map<String, Object> STATIC_CACHE = new HashMap<>();
// 2. 监听器未移除导致的内存泄漏
private final List<EventListener> listeners = new ArrayList<>();
// 3. 线程池未关闭导致的内存泄漏
private ExecutorService executorService;
public static void main(String[] args) throws InterruptedException {
System.out.println("=== Memory Leak Examples ===");
System.out.println("演示常见的内存泄漏场景");
MemoryLeakExamples examples = new MemoryLeakExamples();
// 启动内存监控
MemoryMonitor monitor = new MemoryMonitor();
monitor.start();
// 演示各种内存泄漏
examples.demonstrateStaticCollectionLeak();
Thread.sleep(2000);
examples.demonstrateListenerLeak();
Thread.sleep(2000);
examples.demonstrateThreadPoolLeak();
Thread.sleep(2000);
examples.demonstrateInnerClassLeak();
Thread.sleep(2000);
examples.demonstrateResourceLeak();
Thread.sleep(2000);
monitor.stop();
System.out.println("\n内存泄漏演示完成!");
System.out.println("请在 VisualVM 中观察内存使用情况和生成堆转储进行分析");
}
// 1. 静态集合内存泄漏
public void demonstrateStaticCollectionLeak() {
System.out.println("\n1. 演示静态集合内存泄漏...");
for (int i = 0; i < 10000; i++) {
// 向静态集合添加对象,永远不会被回收
STATIC_LIST.add(new LargeObject("Static Object " + i));
STATIC_CACHE.put("key" + i, new LargeObject("Cached Object " + i));
if (i % 1000 == 0) {
System.out.println("Added " + (i + 1) + " objects to static collections");
}
}
System.out.println("Static collections now contain " + STATIC_LIST.size() + " objects");
System.out.println("⚠️ These objects will never be garbage collected!");
}
// 2. 监听器内存泄漏
public void demonstrateListenerLeak() {
System.out.println("\n2. 演示监听器内存泄漏...");
EventSource eventSource = new EventSource();
for (int i = 0; i < 1000; i++) {
// 创建监听器但不移除,导致内存泄漏
EventListener listener = new EventListener() {
private final LargeObject data = new LargeObject("Listener Data");
@Override
public void onEvent(String event) {
// 处理事件
}
};
eventSource.addListener(listener);
listeners.add(listener);
}
System.out.println("Created " + listeners.size() + " listeners");
System.out.println("⚠️ Listeners are not removed, causing memory leak!");
// 正确的做法应该是:
// eventSource.removeListener(listener);
}
// 3. 线程池内存泄漏
public void demonstrateThreadPoolLeak() {
System.out.println("\n3. 演示线程池内存泄漏...");
for (int pool = 0; pool < 10; pool++) {
// 创建线程池但不关闭
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int task = 0; task < 100; task++) {
final int taskId = task;
executor.submit(() -> {
// 模拟任务执行
LargeObject taskData = new LargeObject("Task Data " + taskId);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
// ⚠️ 没有调用 executor.shutdown(),导致线程池泄漏
System.out.println("Created thread pool " + (pool + 1) + " (not shutdown)");
}
System.out.println("⚠️ Thread pools are not shutdown, causing memory leak!");
}
// 4. 内部类内存泄漏
public void demonstrateInnerClassLeak() {
System.out.println("\n4. 演示内部类内存泄漏...");
List<InnerClassHolder> holders = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
// 非静态内部类持有外部类引用
InnerClassHolder holder = new InnerClassHolder();
InnerClass inner = holder.createInnerClass();
// 只保持内部类引用,但外部类也不会被回收
holders.add(holder);
}
System.out.println("Created " + holders.size() + " inner class instances");
System.out.println("⚠️ Inner classes hold references to outer classes!");
}
// 5. 资源未关闭导致的内存泄漏
public void demonstrateResourceLeak() {
System.out.println("\n5. 演示资源未关闭内存泄漏...");
for (int i = 0; i < 100; i++) {
try {
// 创建资源但不关闭
LeakyResource resource = new LeakyResource("Resource " + i);
resource.doSomething();
// ⚠️ 没有调用 resource.close(),导致资源泄漏
} catch (Exception e) {
System.err.println("Error creating resource: " + e.getMessage());
}
}
System.out.println("Created 100 resources without closing them");
System.out.println("⚠️ Resources are not closed, causing memory leak!");
}
// 大对象类
static class LargeObject {
private final String name;
private final byte[] data;
public LargeObject(String name) {
this.name = name;
this.data = new byte[10240]; // 10KB
Arrays.fill(data, (byte) name.hashCode());
}
@Override
public String toString() {
return "LargeObject{name='" + name + "', size=" + data.length + "}";
}
}
// 事件监听器接口
interface EventListener {
void onEvent(String event);
}
// 事件源
static class EventSource {
private final List<EventListener> listeners = new ArrayList<>();
public void addListener(EventListener listener) {
listeners.add(listener);
}
public void removeListener(EventListener listener) {
listeners.remove(listener);
}
public void fireEvent(String event) {
for (EventListener listener : listeners) {
listener.onEvent(event);
}
}
}
// 内部类持有者
class InnerClassHolder {
private final LargeObject data = new LargeObject("Holder Data");
public InnerClass createInnerClass() {
return new InnerClass();
}
// 非静态内部类(持有外部类引用)
class InnerClass {
private final LargeObject innerData = new LargeObject("Inner Data");
public void doSomething() {
// 可以访问外部类的成员
System.out.println("Inner class accessing outer: " + data);
}
}
}
// 泄漏资源类
static class LeakyResource {
private final String name;
private final Timer timer;
private final LargeObject resource;
public LeakyResource(String name) {
this.name = name;
this.resource = new LargeObject("Resource Data");
this.timer = new Timer("Resource Timer " + name, false);
// 启动定时任务
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
// 模拟资源使用
}
}, 0, 1000);
}
public void doSomething() {
System.out.println("Using resource: " + name);
}
// 应该调用但经常被忘记的清理方法
public void close() {
if (timer != null) {
timer.cancel();
}
}
}
// 内存监控器
static class MemoryMonitor {
private Timer timer;
private volatile boolean running;
public void start() {
running = true;
timer = new Timer("MemoryMonitor", true);
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
if (running) {
printMemoryUsage();
}
}
}, 0, 5000);
}
public void stop() {
running = false;
if (timer != null) {
timer.cancel();
}
}
private void printMemoryUsage() {
Runtime runtime = Runtime.getRuntime();
long totalMemory = runtime.totalMemory();
long freeMemory = runtime.freeMemory();
long usedMemory = totalMemory - freeMemory;
long maxMemory = runtime.maxMemory();
double usagePercent = (double) usedMemory / maxMemory * 100;
System.out.printf("[Memory] Used: %.1f MB (%.1f%%), Free: %.1f MB, Total: %.1f MB%n",
usedMemory / 1024.0 / 1024.0,
usagePercent,
freeMemory / 1024.0 / 1024.0,
totalMemory / 1024.0 / 1024.0);
if (usagePercent > 80) {
System.out.println("⚠️ High memory usage detected!");
}
}
}
}
4.2 内存泄漏检测策略
4.2.1 使用 VisualVM 检测内存泄漏
检测步骤:
- 长期监控:连接应用程序,观察内存使用趋势
- 多次 GC:手动触发垃圾回收,观察内存是否下降
- 堆转储对比:在不同时间点生成堆转储,对比分析
- 对象增长分析:关注持续增长的对象类型
4.2.2 内存泄漏检测工具
// 内存泄漏检测工具
import java.lang.management.*;
import java.util.*;
import java.util.concurrent.*;
public class MemoryLeakDetector {
private final MemoryMXBean memoryBean;
private final List<GarbageCollectorMXBean> gcBeans;
private final ScheduledExecutorService scheduler;
private final List<MemorySnapshot> snapshots;
private final Map<String, Long> classInstanceCounts;
public MemoryLeakDetector() {
this.memoryBean = ManagementFactory.getMemoryMXBean();
this.gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
this.scheduler = Executors.newScheduledThreadPool(1);
this.snapshots = new ArrayList<>();
this.classInstanceCounts = new ConcurrentHashMap<>();
}
public void startDetection(long intervalSeconds) {
System.out.println("Starting memory leak detection...");
scheduler.scheduleAtFixedRate(() -> {
try {
collectSnapshot();
analyzeLeakPatterns();
} catch (Exception e) {
System.err.println("Error during leak detection: " + e.getMessage());
}
}, 0, intervalSeconds, TimeUnit.SECONDS);
}
public void stopDetection() {
scheduler.shutdown();
try {
if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
scheduler.shutdownNow();
}
} catch (InterruptedException e) {
scheduler.shutdownNow();
Thread.currentThread().interrupt();
}
}
private void collectSnapshot() {
MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
long totalGcCount = 0;
long totalGcTime = 0;
for (GarbageCollectorMXBean gcBean : gcBeans) {
totalGcCount += gcBean.getCollectionCount();
totalGcTime += gcBean.getCollectionTime();
}
MemorySnapshot snapshot = new MemorySnapshot(
System.currentTimeMillis(),
heapUsage.getUsed(),
heapUsage.getCommitted(),
heapUsage.getMax(),
totalGcCount,
totalGcTime
);
snapshots.add(snapshot);
// 保持最近的 100 个快照
if (snapshots.size() > 100) {
snapshots.remove(0);
}
}
private void analyzeLeakPatterns() {
if (snapshots.size() < 10) {
return; // 需要足够的数据点
}
// 分析内存增长趋势
analyzeMemoryTrend();
// 分析 GC 效果
analyzeGCEffectiveness();
// 检测内存泄漏警告
detectLeakWarnings();
}
private void analyzeMemoryTrend() {
if (snapshots.size() < 5) return;
// 计算最近 5 个快照的内存增长趋势
List<MemorySnapshot> recent = snapshots.subList(
Math.max(0, snapshots.size() - 5), snapshots.size());
long startMemory = recent.get(0).heapUsed;
long endMemory = recent.get(recent.size() - 1).heapUsed;
long timeDiff = recent.get(recent.size() - 1).timestamp - recent.get(0).timestamp;
if (timeDiff > 0) {
double growthRate = (double) (endMemory - startMemory) / timeDiff * 1000; // bytes per second
if (growthRate > 1024 * 1024) { // > 1MB/s
System.out.printf("⚠️ High memory growth rate: %.2f MB/s%n",
growthRate / 1024 / 1024);
}
}
}
private void analyzeGCEffectiveness() {
if (snapshots.size() < 3) return;
MemorySnapshot current = snapshots.get(snapshots.size() - 1);
MemorySnapshot previous = snapshots.get(snapshots.size() - 2);
// 检查 GC 是否发生
if (current.totalGcCount > previous.totalGcCount) {
long gcCount = current.totalGcCount - previous.totalGcCount;
long memoryBefore = previous.heapUsed;
long memoryAfter = current.heapUsed;
if (memoryAfter >= memoryBefore * 0.9) { // GC 后内存只减少了不到 10%
System.out.printf("⚠️ Ineffective GC: %d collections, memory %.1f MB -> %.1f MB%n",
gcCount,
memoryBefore / 1024.0 / 1024.0,
memoryAfter / 1024.0 / 1024.0);
}
}
}
private void detectLeakWarnings() {
if (snapshots.size() < 20) return;
// 检查长期内存增长趋势
MemorySnapshot start = snapshots.get(0);
MemorySnapshot end = snapshots.get(snapshots.size() - 1);
double memoryGrowth = (double) (end.heapUsed - start.heapUsed) / start.heapUsed * 100;
long timeDiff = end.timestamp - start.timestamp;
if (memoryGrowth > 50 && timeDiff > 60000) { // 内存增长超过 50% 且时间超过 1 分钟
System.out.printf("🚨 MEMORY LEAK WARNING: Memory increased by %.1f%% over %.1f minutes%n",
memoryGrowth, timeDiff / 60000.0);
// 建议生成堆转储
System.out.println("💡 Recommendation: Generate heap dump for analysis");
System.out.println(" Use: jcmd <pid> GC.dump_heap /path/to/heap_dump.hprof");
}
// 检查内存使用率
double memoryUsage = (double) end.heapUsed / end.heapMax * 100;
if (memoryUsage > 85) {
System.out.printf("🚨 HIGH MEMORY USAGE: %.1f%% of heap used%n", memoryUsage);
}
}
public void printDetectionSummary() {
if (snapshots.isEmpty()) {
System.out.println("No detection data available");
return;
}
System.out.println("\n=== Memory Leak Detection Summary ===");
MemorySnapshot first = snapshots.get(0);
MemorySnapshot last = snapshots.get(snapshots.size() - 1);
long timeDiff = last.timestamp - first.timestamp;
long memoryDiff = last.heapUsed - first.heapUsed;
System.out.printf("Detection period: %.1f minutes%n", timeDiff / 60000.0);
System.out.printf("Memory change: %+.2f MB%n", memoryDiff / 1024.0 / 1024.0);
System.out.printf("GC collections: %d%n", last.totalGcCount - first.totalGcCount);
System.out.printf("GC time: %d ms%n", last.totalGcTime - first.totalGcTime);
// 计算平均内存使用率
double avgMemoryUsage = snapshots.stream()
.mapToDouble(s -> (double) s.heapUsed / s.heapMax * 100)
.average().orElse(0);
System.out.printf("Average memory usage: %.1f%%%n", avgMemoryUsage);
// 检测结果
if (memoryDiff > 50 * 1024 * 1024 && timeDiff > 300000) { // 50MB 增长且超过 5 分钟
System.out.println("\n🚨 POTENTIAL MEMORY LEAK DETECTED!");
System.out.println("Recommendations:");
System.out.println(" 1. Generate and analyze heap dumps");
System.out.println(" 2. Review static collections and caches");
System.out.println(" 3. Check for unclosed resources");
System.out.println(" 4. Verify listener cleanup");
} else {
System.out.println("\n✅ No obvious memory leaks detected");
}
}
// 内存快照数据结构
static class MemorySnapshot {
final long timestamp;
final long heapUsed;
final long heapCommitted;
final long heapMax;
final long totalGcCount;
final long totalGcTime;
public MemorySnapshot(long timestamp, long heapUsed, long heapCommitted,
long heapMax, long totalGcCount, long totalGcTime) {
this.timestamp = timestamp;
this.heapUsed = heapUsed;
this.heapCommitted = heapCommitted;
this.heapMax = heapMax;
this.totalGcCount = totalGcCount;
this.totalGcTime = totalGcTime;
}
}
public static void main(String[] args) throws InterruptedException {
MemoryLeakDetector detector = new MemoryLeakDetector();
// 启动检测(每 10 秒检查一次)
detector.startDetection(10);
// 模拟应用程序运行
System.out.println("Simulating application with potential memory leaks...");
// 运行一些可能导致内存泄漏的代码
MemoryLeakExamples examples = new MemoryLeakExamples();
examples.demonstrateStaticCollectionLeak();
// 运行 5 分钟
Thread.sleep(300000);
detector.stopDetection();
detector.printDetectionSummary();
}
}
5. 内存优化策略
5.1 对象生命周期管理
5.1.1 对象池模式
// 对象池实现示例
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class ObjectPoolExample {
// 重对象示例
static class ExpensiveObject {
private final int id;
private final byte[] data;
private volatile boolean inUse;
public ExpensiveObject(int id) {
this.id = id;
this.data = new byte[10240]; // 10KB
this.inUse = false;
// 模拟昂贵的初始化过程
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public void reset() {
// 重置对象状态以便重用
java.util.Arrays.fill(data, (byte) 0);
inUse = false;
}
public void use() {
inUse = true;
// 模拟对象使用
java.util.Arrays.fill(data, (byte) id);
}
public boolean isInUse() {
return inUse;
}
public int getId() {
return id;
}
}
// 简单对象池实现
static class SimpleObjectPool {
private final BlockingQueue<ExpensiveObject> pool;
private final AtomicInteger createdCount;
private final int maxSize;
public SimpleObjectPool(int maxSize) {
this.pool = new LinkedBlockingQueue<>(maxSize);
this.createdCount = new AtomicInteger(0);
this.maxSize = maxSize;
}
public ExpensiveObject acquire() throws InterruptedException {
ExpensiveObject obj = pool.poll();
if (obj == null) {
// 池中没有可用对象,创建新对象
if (createdCount.get() < maxSize) {
obj = new ExpensiveObject(createdCount.incrementAndGet());
System.out.println("Created new object: " + obj.getId());
} else {
// 等待可用对象
obj = pool.take();
System.out.println("Reused object from pool: " + obj.getId());
}
} else {
System.out.println("Got object from pool: " + obj.getId());
}
return obj;
}
public void release(ExpensiveObject obj) {
if (obj != null) {
obj.reset();
pool.offer(obj);
System.out.println("Returned object to pool: " + obj.getId());
}
}
public int getPoolSize() {
return pool.size();
}
public int getCreatedCount() {
return createdCount.get();
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("=== Object Pool Example ===");
SimpleObjectPool pool = new SimpleObjectPool(5);
// 模拟多线程使用对象池
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 20; i++) {
final int taskId = i;
executor.submit(() -> {
try {
ExpensiveObject obj = pool.acquire();
// 使用对象
obj.use();
Thread.sleep(100); // 模拟使用时间
// 归还对象
pool.release(obj);
System.out.printf("Task %d completed, pool size: %d%n",
taskId, pool.getPoolSize());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
executor.shutdown();
executor.awaitTermination(30, TimeUnit.SECONDS);
System.out.printf("\nFinal stats - Created objects: %d, Pool size: %d%n",
pool.getCreatedCount(), pool.getPoolSize());
}
}
5.1.2 弱引用和软引用
// 引用类型示例
import java.lang.ref.*;
import java.util.*;
import java.util.concurrent.*;
public class ReferenceTypesExample {
// 缓存数据类
static class CacheData {
private final String key;
private final byte[] data;
private final long timestamp;
public CacheData(String key, int size) {
this.key = key;
this.data = new byte[size];
this.timestamp = System.currentTimeMillis();
Arrays.fill(data, (byte) key.hashCode());
}
public String getKey() { return key; }
public byte[] getData() { return data; }
public long getTimestamp() { return timestamp; }
@Override
public String toString() {
return String.format("CacheData{key='%s', size=%d, age=%dms}",
key, data.length, System.currentTimeMillis() - timestamp);
}
}
// 使用软引用的缓存
static class SoftReferenceCache {
private final Map<String, SoftReference<CacheData>> cache;
private final ReferenceQueue<CacheData> referenceQueue;
public SoftReferenceCache() {
this.cache = new ConcurrentHashMap<>();
this.referenceQueue = new ReferenceQueue<>();
}
public void put(String key, CacheData data) {
cleanupStaleReferences();
cache.put(key, new SoftReference<>(data, referenceQueue));
System.out.println("Cached: " + key);
}
public CacheData get(String key) {
SoftReference<CacheData> ref = cache.get(key);
if (ref != null) {
CacheData data = ref.get();
if (data != null) {
System.out.println("Cache hit: " + key);
return data;
} else {
// 软引用已被回收
cache.remove(key);
System.out.println("Cache miss (GC'd): " + key);
}
} else {
System.out.println("Cache miss: " + key);
}
return null;
}
private void cleanupStaleReferences() {
Reference<? extends CacheData> ref;
while ((ref = referenceQueue.poll()) != null) {
// 清理已被回收的引用
cache.values().removeIf(softRef -> softRef == ref);
}
}
public int size() {
cleanupStaleReferences();
return cache.size();
}
public void printStats() {
cleanupStaleReferences();
System.out.printf("Cache size: %d entries%n", cache.size());
long totalSize = cache.values().stream()
.mapToLong(ref -> {
CacheData data = ref.get();
return data != null ? data.getData().length : 0;
})
.sum();
System.out.printf("Total cached data: %.2f MB%n", totalSize / 1024.0 / 1024.0);
}
}
// 使用弱引用的监听器管理
static class WeakListenerManager {
private final List<WeakReference<EventListener>> listeners;
public WeakListenerManager() {
this.listeners = new ArrayList<>();
}
public void addListener(EventListener listener) {
listeners.add(new WeakReference<>(listener));
System.out.println("Added listener: " + listener.getClass().getSimpleName());
}
public void fireEvent(String event) {
Iterator<WeakReference<EventListener>> iterator = listeners.iterator();
int activeListeners = 0;
while (iterator.hasNext()) {
WeakReference<EventListener> ref = iterator.next();
EventListener listener = ref.get();
if (listener != null) {
listener.onEvent(event);
activeListeners++;
} else {
// 监听器已被回收,移除弱引用
iterator.remove();
}
}
System.out.printf("Event '%s' fired to %d listeners%n", event, activeListeners);
}
public int getListenerCount() {
// 清理已回收的引用
listeners.removeIf(ref -> ref.get() == null);
return listeners.size();
}
}
interface EventListener {
void onEvent(String event);
}
static class MyEventListener implements EventListener {
private final String name;
private final byte[] data;
public MyEventListener(String name) {
this.name = name;
this.data = new byte[1024]; // 1KB
}
@Override
public void onEvent(String event) {
System.out.println(name + " received event: " + event);
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("=== Reference Types Example ===");
// 演示软引用缓存
demonstrateSoftReferenceCache();
System.out.println("\n" + "=".repeat(50));
// 演示弱引用监听器
demonstrateWeakReferenceListeners();
}
private static void demonstrateSoftReferenceCache() throws InterruptedException {
System.out.println("\n=== Soft Reference Cache Demo ===");
SoftReferenceCache cache = new SoftReferenceCache();
// 添加一些缓存数据
for (int i = 0; i < 100; i++) {
String key = "data" + i;
CacheData data = new CacheData(key, 1024 * 1024); // 1MB per item
cache.put(key, data);
}
cache.printStats();
// 访问一些缓存项
System.out.println("\nAccessing cached items:");
for (int i = 0; i < 10; i++) {
cache.get("data" + i);
}
// 创建内存压力,触发软引用回收
System.out.println("\nCreating memory pressure...");
try {
List<byte[]> memoryPressure = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
memoryPressure.add(new byte[1024 * 1024]); // 1MB
}
} catch (OutOfMemoryError e) {
System.out.println("OutOfMemoryError caught (expected)");
}
// 强制 GC
System.gc();
Thread.sleep(1000);
System.out.println("\nAfter GC:");
cache.printStats();
// 再次访问缓存项
System.out.println("\nAccessing cached items after GC:");
for (int i = 0; i < 10; i++) {
cache.get("data" + i);
}
}
private static void demonstrateWeakReferenceListeners() throws InterruptedException {
System.out.println("\n=== Weak Reference Listeners Demo ===");
WeakListenerManager manager = new WeakListenerManager();
// 添加一些监听器
for (int i = 0; i < 10; i++) {
MyEventListener listener = new MyEventListener("Listener" + i);
manager.addListener(listener);
// 只保持前 5 个监听器的强引用
if (i >= 5) {
// 让后 5 个监听器可以被 GC
listener = null;
}
}
System.out.printf("\nInitial listener count: %d%n", manager.getListenerCount());
// 触发事件
manager.fireEvent("Initial Event");
// 强制 GC
System.out.println("\nForcing garbage collection...");
System.gc();
Thread.sleep(1000);
System.out.printf("Listener count after GC: %d%n", manager.getListenerCount());
// 再次触发事件
manager.fireEvent("Post-GC Event");
}
}
5.2 内存使用优化
5.2.1 字符串优化
// 字符串内存优化示例
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class StringOptimizationExample {
// 字符串池实现
static class StringPool {
private final Map<String, String> pool = new ConcurrentHashMap<>();
private long memoryBefore;
private long memoryAfter;
public String intern(String str) {
if (str == null) return null;
return pool.computeIfAbsent(str, k -> k);
}
public int size() {
return pool.size();
}
public void clear() {
pool.clear();
}
public void measureMemoryBefore() {
System.gc();
try { Thread.sleep(100); } catch (InterruptedException e) {}
memoryBefore = getUsedMemory();
}
public void measureMemoryAfter() {
System.gc();
try { Thread.sleep(100); } catch (InterruptedException e) {}
memoryAfter = getUsedMemory();
}
public void printMemoryStats() {
long memoryDiff = memoryAfter - memoryBefore;
System.out.printf("Memory usage: %.2f MB -> %.2f MB (diff: %+.2f MB)%n",
memoryBefore / 1024.0 / 1024.0,
memoryAfter / 1024.0 / 1024.0,
memoryDiff / 1024.0 / 1024.0);
System.out.printf("String pool size: %d unique strings%n", size());
}
private long getUsedMemory() {
Runtime runtime = Runtime.getRuntime();
return runtime.totalMemory() - runtime.freeMemory();
}
}
public static void main(String[] args) {
System.out.println("=== String Optimization Example ===");
// 演示字符串重复问题
demonstrateStringDuplication();
System.out.println("\n" + "=".repeat(50));
// 演示字符串池优化
demonstrateStringPoolOptimization();
System.out.println("\n" + "=".repeat(50));
// 演示 StringBuilder 优化
demonstrateStringBuilderOptimization();
}
private static void demonstrateStringDuplication() {
System.out.println("\n=== String Duplication Problem ===");
StringPool pool = new StringPool();
pool.measureMemoryBefore();
// 创建大量重复字符串(不使用池)
List<String> duplicateStrings = new ArrayList<>();
String[] patterns = {"user", "admin", "guest", "manager", "developer"};
for (int i = 0; i < 10000; i++) {
for (String pattern : patterns) {
// 每次都创建新的字符串对象
String str = new String(pattern + "_" + (i % 100));
duplicateStrings.add(str);
}
}
pool.measureMemoryAfter();
System.out.printf("Created %d strings with duplication%n", duplicateStrings.size());
pool.printMemoryStats();
}
private static void demonstrateStringPoolOptimization() {
System.out.println("\n=== String Pool Optimization ===");
StringPool pool = new StringPool();
pool.measureMemoryBefore();
// 使用字符串池减少重复
List<String> pooledStrings = new ArrayList<>();
String[] patterns = {"user", "admin", "guest", "manager", "developer"};
for (int i = 0; i < 10000; i++) {
for (String pattern : patterns) {
String str = pool.intern(pattern + "_" + (i % 100));
pooledStrings.add(str);
}
}
pool.measureMemoryAfter();
System.out.printf("Created %d strings with pooling%n", pooledStrings.size());
pool.printMemoryStats();
}
private static void demonstrateStringBuilderOptimization() {
System.out.println("\n=== StringBuilder Optimization ===");
// 错误的字符串拼接方式
long startTime = System.currentTimeMillis();
String result1 = "";
for (int i = 0; i < 1000; i++) {
result1 += "Item " + i + ", ";
}
long time1 = System.currentTimeMillis() - startTime;
// 正确的字符串拼接方式
startTime = System.currentTimeMillis();
StringBuilder sb = new StringBuilder(50000); // 预分配容量
for (int i = 0; i < 1000; i++) {
sb.append("Item ").append(i).append(", ");
}
String result2 = sb.toString();
long time2 = System.currentTimeMillis() - startTime;
System.out.printf("String concatenation: %d ms%n", time1);
System.out.printf("StringBuilder: %d ms%n", time2);
System.out.printf("Performance improvement: %.1fx faster%n", (double) time1 / time2);
// 验证结果相同
System.out.printf("Results equal: %b%n", result1.equals(result2));
System.out.printf("Result length: %d characters%n", result2.length());
}
}
6. 实践练习
练习 1:内存使用监控
目标:使用 VisualVM 监控应用程序的内存使用模式
步骤:
- 创建测试应用:
// MemoryMonitoringExample.java
import java.util.*;
import java.util.concurrent.*;
public class MemoryMonitoringExample {
private static final List<byte[]> memoryConsumer = new ArrayList<>();
private static final Random random = new Random();
public static void main(String[] args) throws InterruptedException {
System.out.println("Memory Monitoring Example Started");
System.out.println("PID: " + ProcessHandle.current().pid());
// 阶段 1:稳定内存使用
System.out.println("Phase 1: Stable memory usage");
for (int i = 0; i < 100; i++) {
memoryConsumer.add(new byte[1024 * 100]); // 100KB
Thread.sleep(100);
}
// 阶段 2:内存增长
System.out.println("Phase 2: Memory growth");
for (int i = 0; i < 200; i++) {
memoryConsumer.add(new byte[1024 * 500]); // 500KB
Thread.sleep(50);
}
// 阶段 3:内存释放
System.out.println("Phase 3: Memory release");
for (int i = 0; i < 150; i++) {
if (!memoryConsumer.isEmpty()) {
memoryConsumer.remove(random.nextInt(memoryConsumer.size()));
}
Thread.sleep(30);
}
// 阶段 4:GC 压力测试
System.out.println("Phase 4: GC pressure test");
for (int i = 0; i < 1000; i++) {
// 创建短生命周期对象
byte[] temp = new byte[1024 * 1024]; // 1MB
Arrays.fill(temp, (byte) i);
Thread.sleep(10);
}
System.out.println("Example completed. Press Ctrl+C to exit.");
Thread.sleep(Long.MAX_VALUE);
}
}
监控步骤:
- 编译并运行测试应用
- 在 VisualVM 中连接到应用程序
- 观察 Monitor 标签页中的内存使用图表
- 记录各个阶段的内存使用模式
- 观察 GC 活动和频率
分析要点:
- 堆内存的增长和释放模式
- GC 的触发时机和效果
- 不同代的内存使用情况
- 内存使用的峰值和平均值
练习 2:堆转储分析
目标:生成和分析堆转储,识别内存使用热点
步骤:
- 创建内存泄漏示例:
// HeapDumpExample.java
import java.util.*;
import java.util.concurrent.*;
public class HeapDumpExample {
private static final Map<String, List<byte[]>> cache = new ConcurrentHashMap<>();
private static final ScheduledExecutorService scheduler =
Executors.newScheduledThreadPool(2);
public static void main(String[] args) throws InterruptedException {
System.out.println("Heap Dump Example Started");
System.out.println("PID: " + ProcessHandle.current().pid());
// 启动数据生产者
scheduler.scheduleAtFixedRate(() -> {
String key = "data_" + System.currentTimeMillis();
List<byte[]> data = new ArrayList<>();
// 每次添加 10MB 数据
for (int i = 0; i < 10; i++) {
data.add(new byte[1024 * 1024]); // 1MB
}
cache.put(key, data);
System.out.printf("Added %s, cache size: %d%n", key, cache.size());
}, 0, 2, TimeUnit.SECONDS);
// 启动数据清理者(但清理不及时,造成内存泄漏)
scheduler.scheduleAtFixedRate(() -> {
if (cache.size() > 3) {
String oldestKey = cache.keySet().iterator().next();
cache.remove(oldestKey);
System.out.printf("Removed %s, cache size: %d%n", oldestKey, cache.size());
}
}, 10, 10, TimeUnit.SECONDS);
// 保持程序运行
Thread.sleep(Long.MAX_VALUE);
}
}
堆转储分析步骤:
- 运行示例程序 5-10 分钟
- 在 VisualVM 中生成堆转储
- 分析 Classes 视图,找出占用内存最多的类
- 分析 Instances 视图,查看具体对象实例
- 使用 OQL 查询特定对象
- 分析对象的引用关系
分析要点:
- 识别内存占用最大的对象类型
- 分析对象的引用链
- 找出可能的内存泄漏点
- 评估内存使用的合理性
练习 3:内存优化实践
目标:应用内存优化技术,对比优化前后的效果
步骤:
- 创建优化前的代码:
// BeforeOptimization.java
import java.util.*;
public class BeforeOptimization {
private static final List<String> stringList = new ArrayList<>();
private static final List<UserData> userList = new ArrayList<>();
static class UserData {
private String name;
private String email;
private String department;
private List<String> permissions;
public UserData(String name, String email, String department) {
this.name = new String(name); // 不必要的字符串复制
this.email = new String(email);
this.department = new String(department);
this.permissions = new ArrayList<>();
}
public void addPermission(String permission) {
permissions.add(new String(permission)); // 不必要的字符串复制
}
public String getFullInfo() {
String info = "";
info += "Name: " + name + ", ";
info += "Email: " + email + ", ";
info += "Department: " + department + ", ";
info += "Permissions: " + permissions.toString();
return info;
}
}
public static void main(String[] args) {
System.out.println("Before Optimization Example");
// 创建大量重复字符串
String[] departments = {"Engineering", "Marketing", "Sales", "HR", "Finance"};
String[] permissions = {"READ", "write", "admin", "user", "guest"};
for (int i = 0; i < 10000; i++) {
String name = "User" + i;
String email = "user" + i + "@company.com";
String dept = departments[i % departments.length];
UserData user = new UserData(name, email, dept);
// 添加权限
for (int j = 0; j < 3; j++) {
user.addPermission(permissions[j % permissions.length]);
}
userList.add(user);
stringList.add(user.getFullInfo());
}
System.out.printf("Created %d users and %d info strings%n",
userList.size(), stringList.size());
// 保持程序运行以便分析
try {
Thread.sleep(Long.MAX_VALUE);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
- 创建优化后的代码:
// AfterOptimization.java
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class AfterOptimization {
private static final List<String> stringList = new ArrayList<>();
private static final List<UserData> userList = new ArrayList<>();
private static final StringPool stringPool = new StringPool();
// 字符串池
static class StringPool {
private final Map<String, String> pool = new ConcurrentHashMap<>();
public String intern(String str) {
return pool.computeIfAbsent(str, k -> k);
}
}
static class UserData {
private final String name;
private final String email;
private final String department;
private final List<String> permissions;
public UserData(String name, String email, String department) {
this.name = stringPool.intern(name);
this.email = stringPool.intern(email);
this.department = stringPool.intern(department);
this.permissions = new ArrayList<>();
}
public void addPermission(String permission) {
permissions.add(stringPool.intern(permission));
}
public String getFullInfo() {
StringBuilder sb = new StringBuilder(200); // 预分配容量
sb.append("Name: ").append(name).append(", ");
sb.append("Email: ").append(email).append(", ");
sb.append("Department: ").append(department).append(", ");
sb.append("Permissions: ").append(permissions.toString());
return sb.toString();
}
}
public static void main(String[] args) {
System.out.println("After Optimization Example");
// 预先将常用字符串加入池中
String[] departments = {"Engineering", "Marketing", "Sales", "HR", "Finance"};
String[] permissions = {"read", "write", "admin", "user", "guest"};
for (String dept : departments) {
stringPool.intern(dept);
}
for (String perm : permissions) {
stringPool.intern(perm);
}
for (int i = 0; i < 10000; i++) {
String name = "User" + i;
String email = "user" + i + "@company.com";
String dept = departments[i % departments.length];
UserData user = new UserData(name, email, dept);
// 添加权限
for (int j = 0; j < 3; j++) {
user.addPermission(permissions[j % permissions.length]);
}
userList.add(user);
stringList.add(user.getFullInfo());
}
System.out.printf("Created %d users and %d info strings%n",
userList.size(), stringList.size());
// 保持程序运行以便分析
try {
Thread.sleep(Long.MAX_VALUE);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
对比分析步骤:
- 分别运行优化前后的代码
- 使用 VisualVM 监控内存使用
- 生成堆转储进行对比
- 分析字符串对象的数量和内存占用
- 记录 GC 活动的差异
分析要点:
- 内存使用量的减少
- 对象数量的变化
- GC 频率和耗时的改善
- 字符串去重的效果
7. 本章总结
7.1 关键要点
Java 内存模型理解:
- 掌握 JVM 内存结构(堆、栈、方法区等)
- 理解垃圾回收机制和分代收集
- 了解不同内存区域的作用和特点
VisualVM 内存监控:
- 熟练使用 Monitor 标签页监控内存使用
- 理解内存使用图表和 GC 活动指标
- 掌握实时内存监控的技巧
堆转储分析:
- 掌握堆转储的生成方法(手动、程序化、命令行)
- 熟练使用 VisualVM 分析堆转储
- 理解对象引用关系和内存占用分布
内存泄漏检测:
- 识别常见的内存泄漏模式
- 使用 VisualVM 检测内存泄漏
- 掌握内存泄漏的分析和定位技巧
内存优化策略:
- 对象生命周期管理(对象池、引用类型)
- 字符串优化(字符串池、StringBuilder)
- 集合类优化和容量预分配
7.2 最佳实践
监控策略:
- 建立内存监控基线
- 定期进行内存使用分析
- 设置合理的内存告警阈值
- 结合业务场景分析内存使用模式
内存泄漏预防:
- 及时释放不再使用的对象引用
- 正确使用集合类,避免无限增长
- 合理使用缓存,设置过期策略
- 注意监听器和回调的生命周期管理
性能优化:
- 选择合适的数据结构和算法
- 合理使用对象池和缓存
- 优化字符串操作和拼接
- 预分配集合容量,减少扩容开销
故障诊断:
- 建立内存问题的诊断流程
- 保存关键时刻的堆转储
- 结合日志和监控数据分析
- 制定内存问题的应急处理方案
7.3 进阶学习
完成本章学习后,建议继续深入以下主题:
高级内存分析:
- JVM 内存参数调优
- 不同垃圾收集器的特点和选择
- 内存映射文件和直接内存
- 大对象和长生命周期对象的处理
生产环境实践:
- 生产环境内存监控方案
- 内存问题的自动化检测
- 内存使用的容量规划
- 内存相关的性能基准测试
工具集成:
- 与 APM 工具的集成
- 自动化内存分析脚本
- CI/CD 中的内存测试
- 内存分析报告的自动生成
下一章我们将学习 CPU 性能分析与采样,探讨如何使用 VisualVM 分析应用程序的 CPU 使用情况,识别性能瓶颈,并进行针对性的优化。 “`