第6章:垃圾回收分析与调优
1. 垃圾回收基础
1.1 Java 垃圾回收概述
垃圾回收(Garbage Collection,GC)是 Java 虚拟机自动内存管理的核心机制。理解 GC 的工作原理对于 Java 应用性能调优至关重要。
1.1.1 垃圾回收的目标
- 自动内存管理:无需手动释放内存
- 防止内存泄漏:回收不再使用的对象
- 优化内存使用:整理内存碎片
- 保证应用稳定性:避免内存溢出
1.1.2 垃圾回收的挑战
- 停顿时间:GC 过程中应用暂停
- 吞吐量影响:GC 消耗 CPU 资源
- 内存开销:GC 算法本身需要内存
- 调优复杂性:参数众多,相互影响
1.2 垃圾回收算法
1.2.1 标记-清除算法(Mark-Sweep)
// 标记-清除算法演示
import java.util.*;
import java.lang.ref.*;public class MarkSweepDemo {private static final List<Object> roots = new ArrayList<>();private static final Set<Object> markedObjects = new HashSet<>();public static void main(String[] args) {System.out.println("=== Mark-Sweep Algorithm Demo ===");// 创建对象图createObjectGraph();// 模拟标记阶段markPhase();// 模拟清除阶段sweepPhase();System.out.println("Mark-Sweep completed");}private static void createObjectGraph() {// 创建根对象Node root1 = new Node("Root1");Node root2 = new Node("Root2");// 创建可达对象Node child1 = new Node("Child1");Node child2 = new Node("Child2");Node grandChild = new Node("GrandChild");// 建立引用关系root1.addChild(child1);root1.addChild(child2);child1.addChild(grandChild);// 创建不可达对象Node orphan1 = new Node("Orphan1");Node orphan2 = new Node("Orphan2");orphan1.addChild(orphan2);// 添加到根集合roots.add(root1);roots.add(root2);System.out.println("Object graph created");}private static void markPhase() {System.out.println("\n=== Mark Phase ===");// 从根对象开始标记for (Object root : roots) {markReachable(root);}System.out.println("Marked " + markedObjects.size() + " objects");}private static void markReachable(Object obj) {if (obj == null || markedObjects.contains(obj)) {return;}// 标记当前对象markedObjects.add(obj);System.out.println("Marked: " + obj);// 递归标记子对象if (obj instanceof Node) {Node node = (Node) obj;for (Node child : node.getChildren()) {markReachable(child);}}}private static void sweepPhase() {System.out.println("\n=== Sweep Phase ===");// 在实际 JVM 中,这里会遍历整个堆// 清除未标记的对象System.out.println("Sweeping unmarked objects...");// 清理标记markedObjects.clear();System.out.println("Sweep phase completed");}static class Node {private final String name;private final List<Node> children = new ArrayList<>();public Node(String name) {this.name = name;}public void addChild(Node child) {children.add(child);}public List<Node> getChildren() {return children;}@Overridepublic String toString() {return "Node{" + name + "}";}}
}
垃圾回收器选择与调优
常见垃圾回收器对比
垃圾回收器 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
Serial GC | 单核CPU,小内存应用 | 简单,内存占用少 | 停顿时间长 |
Parallel GC | 多核CPU,吞吐量优先 | 高吞吐量 | 停顿时间较长 |
CMS GC | 低延迟要求 | 并发收集,停顿时间短 | 内存碎片,CPU占用高 |
G1 GC | 大内存,低延迟 | 可预测停顿时间 | 复杂度高 |
ZGC/Shenandoah | 超大内存,极低延迟 | 极短停顿时间 | 吞吐量较低 |
GC 参数调优示例
// GCTuningExample.java
public class GCTuningExample {private static final int OBJECT_COUNT = 1000000;private static final int ITERATIONS = 10;public static void main(String[] args) {System.out.println("GC调优示例程序启动");System.out.println("JVM参数: " + java.lang.management.ManagementFactory.getRuntimeMXBean().getInputArguments());// 预热JVMwarmUp();// 执行测试long startTime = System.currentTimeMillis();for (int i = 0; i < ITERATIONS; i++) {performMemoryIntensiveTask(i);System.gc(); // 建议进行垃圾回收}long endTime = System.currentTimeMillis();System.out.println("总执行时间: " + (endTime - startTime) + "ms");printMemoryInfo();}private static void warmUp() {System.out.println("JVM预热中...");for (int i = 0; i < 3; i++) {createObjects(OBJECT_COUNT / 10);}System.gc();}private static void performMemoryIntensiveTask(int iteration) {System.out.println("执行第 " + (iteration + 1) + " 次内存密集任务");// 创建大量对象List<String> objects = createObjects(OBJECT_COUNT);// 模拟对象使用processObjects(objects);// 清理引用objects.clear();objects = null;}private static List<String> createObjects(int count) {List<String> objects = new ArrayList<>(count);for (int i = 0; i < count; i++) {objects.add("Object_" + i + "_" + System.nanoTime());}return objects;}private static void processObjects(List<String> objects) {// 模拟对象处理int sum = 0;for (String obj : objects) {sum += obj.hashCode();}// 防止编译器优化if (sum == Integer.MAX_VALUE) {System.out.println("Unlikely case");}}private static void printMemoryInfo() {Runtime runtime = Runtime.getRuntime();long totalMemory = runtime.totalMemory();long freeMemory = runtime.freeMemory();long usedMemory = totalMemory - freeMemory;System.out.println("\n=== 内存使用情况 ===");System.out.println("总内存: " + formatBytes(totalMemory));System.out.println("已用内存: " + formatBytes(usedMemory));System.out.println("空闲内存: " + formatBytes(freeMemory));System.out.println("最大内存: " + formatBytes(runtime.maxMemory()));}private static String formatBytes(long bytes) {if (bytes < 1024) return bytes + " B";if (bytes < 1024 * 1024) return String.format("%.2f KB", bytes / 1024.0);if (bytes < 1024 * 1024 * 1024) return String.format("%.2f MB", bytes / (1024.0 * 1024));return String.format("%.2f GB", bytes / (1024.0 * 1024 * 1024));}
}
不同GC的JVM参数配置
1. Parallel GC(默认)
# 基本配置
java -XX:+UseParallelGC \-XX:ParallelGCThreads=4 \-XX:MaxGCPauseMillis=200 \-Xms2g -Xmx4g \GCTuningExample# 优化配置
java -XX:+UseParallelGC \-XX:+UseParallelOldGC \-XX:ParallelGCThreads=8 \-XX:GCTimeRatio=19 \-XX:MaxGCPauseMillis=100 \-Xms4g -Xmx8g \GCTuningExample
2. G1 GC
# 基本配置
java -XX:+UseG1GC \-XX:MaxGCPauseMillis=100 \-XX:G1HeapRegionSize=16m \-Xms2g -Xmx4g \GCTuningExample# 优化配置
java -XX:+UseG1GC \-XX:MaxGCPauseMillis=50 \-XX:G1HeapRegionSize=32m \-XX:G1NewSizePercent=20 \-XX:G1MaxNewSizePercent=40 \-XX:G1MixedGCCountTarget=8 \-XX:G1MixedGCLiveThresholdPercent=85 \-Xms4g -Xmx8g \GCTuningExample
3. ZGC(Java 11+)
# 基本配置
java -XX:+UseZGC \-XX:+UnlockExperimentalVMOptions \-Xms2g -Xmx4g \GCTuningExample# 优化配置
java -XX:+UseZGC \-XX:+UnlockExperimentalVMOptions \-XX:ZCollectionInterval=5 \-XX:ZUncommitDelay=300 \-Xms8g -Xmx16g \GCTuningExample
GC 性能调优策略
调优步骤
-
建立基线
- 记录当前GC性能指标
- 确定性能目标(吞吐量 vs 延迟)
- 分析应用特征
-
选择合适的GC
- 根据应用需求选择GC算法
- 考虑内存大小和延迟要求
- 评估CPU资源
-
参数调优
- 堆大小调优
- 分代比例调优
- GC线程数调优
-
监控和验证
- 使用VisualVM监控GC性能
- 分析GC日志
- 验证性能改进
GC调优工具类
// GCTuningHelper.java
import java.lang.management.*;
import java.util.*;
import java.util.concurrent.*;public class GCTuningHelper {private static final MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();private static final List<GarbageCollectorMXBean> gcBeans = ManagementFactory.getGarbageCollectorMXBeans();private final Map<String, Long> lastGCCounts = new HashMap<>();private final Map<String, Long> lastGCTimes = new HashMap<>();public void startMonitoring() {// 初始化基线数据for (GarbageCollectorMXBean gcBean : gcBeans) {lastGCCounts.put(gcBean.getName(), gcBean.getCollectionCount());lastGCTimes.put(gcBean.getName(), gcBean.getCollectionTime());}System.out.println("GC监控已启动");printCurrentGCInfo();}public GCMetrics getGCMetrics() {GCMetrics metrics = new GCMetrics();// 内存使用情况MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();metrics.heapUsed = heapUsage.getUsed();metrics.heapMax = heapUsage.getMax();metrics.heapUtilization = (double) heapUsage.getUsed() / heapUsage.getMax() * 100;// GC统计信息for (GarbageCollectorMXBean gcBean : gcBeans) {String gcName = gcBean.getName();long currentCount = gcBean.getCollectionCount();long currentTime = gcBean.getCollectionTime();long lastCount = lastGCCounts.getOrDefault(gcName, 0L);long lastTime = lastGCTimes.getOrDefault(gcName, 0L);GCInfo gcInfo = new GCInfo();gcInfo.name = gcName;gcInfo.totalCollections = currentCount;gcInfo.totalTime = currentTime;gcInfo.recentCollections = currentCount - lastCount;gcInfo.recentTime = currentTime - lastTime;if (gcInfo.recentCollections > 0) {gcInfo.averageTime = (double) gcInfo.recentTime / gcInfo.recentCollections;}metrics.gcInfos.add(gcInfo);// 更新基线lastGCCounts.put(gcName, currentCount);lastGCTimes.put(gcName, currentTime);}return metrics;}public void printGCReport() {GCMetrics metrics = getGCMetrics();System.out.println("\n=== GC性能报告 ===");System.out.printf("堆内存使用: %s / %s (%.2f%%)\n",formatBytes(metrics.heapUsed),formatBytes(metrics.heapMax),metrics.heapUtilization);System.out.println("\nGC统计信息:");for (GCInfo gcInfo : metrics.gcInfos) {System.out.printf(" %s:\n", gcInfo.name);System.out.printf(" 总收集次数: %d\n", gcInfo.totalCollections);System.out.printf(" 总收集时间: %dms\n", gcInfo.totalTime);System.out.printf(" 最近收集次数: %d\n", gcInfo.recentCollections);System.out.printf(" 最近收集时间: %dms\n", gcInfo.recentTime);if (gcInfo.averageTime > 0) {System.out.printf(" 平均收集时间: %.2fms\n", gcInfo.averageTime);}}}public void printCurrentGCInfo() {System.out.println("\n=== 当前GC配置 ===");// 打印GC算法for (GarbageCollectorMXBean gcBean : gcBeans) {System.out.println("GC算法: " + gcBean.getName());}// 打印内存池信息List<MemoryPoolMXBean> memoryPools = ManagementFactory.getMemoryPoolMXBeans();System.out.println("\n内存池信息:");for (MemoryPoolMXBean pool : memoryPools) {MemoryUsage usage = pool.getUsage();if (usage != null) {System.out.printf(" %s: %s / %s\n",pool.getName(),formatBytes(usage.getUsed()),formatBytes(usage.getMax()));}}}private String formatBytes(long bytes) {if (bytes < 0) return "N/A";if (bytes < 1024) return bytes + " B";if (bytes < 1024 * 1024) return String.format("%.2f KB", bytes / 1024.0);if (bytes < 1024 * 1024 * 1024) return String.format("%.2f MB", bytes / (1024.0 * 1024));return String.format("%.2f GB", bytes / (1024.0 * 1024 * 1024));}// 内部类public static class GCMetrics {public long heapUsed;public long heapMax;public double heapUtilization;public List<GCInfo> gcInfos = new ArrayList<>();}public static class GCInfo {public String name;public long totalCollections;public long totalTime;public long recentCollections;public long recentTime;public double averageTime;}
}
## 实践练习### 练习1:GC监控与分析创建一个程序来观察不同GC算法的性能表现:```java
// GCMonitoringExercise.java
import java.util.*;
import java.util.concurrent.*;public class GCMonitoringExercise {private static final int THREAD_COUNT = 4;private static final int OBJECTS_PER_THREAD = 500000;private static final int ITERATIONS = 5;public static void main(String[] args) throws InterruptedException {System.out.println("=== GC监控练习 ===");GCTuningHelper gcHelper = new GCTuningHelper();gcHelper.startMonitoring();// 执行多轮测试for (int round = 1; round <= ITERATIONS; round++) {System.out.println("\n--- 第 " + round + " 轮测试 ---");// 多线程创建对象ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);CountDownLatch latch = new CountDownLatch(THREAD_COUNT);for (int i = 0; i < THREAD_COUNT; i++) {final int threadId = i;executor.submit(() -> {try {createObjectsInThread(threadId, OBJECTS_PER_THREAD);} finally {latch.countDown();}});}latch.await();executor.shutdown();// 强制GC并等待System.gc();Thread.sleep(1000);// 打印GC报告gcHelper.printGCReport();}System.out.println("\n=== 练习完成 ===");}private static void createObjectsInThread(int threadId, int objectCount) {List<String> objects = new ArrayList<>();Random random = new Random();for (int i = 0; i < objectCount; i++) {// 创建不同大小的字符串int size = random.nextInt(100) + 10;StringBuilder sb = new StringBuilder(size);for (int j = 0; j < size; j++) {sb.append((char) ('a' + random.nextInt(26)));}objects.add(sb.toString());// 偶尔清理一些对象if (i % 10000 == 0 && !objects.isEmpty()) {objects.subList(0, Math.min(1000, objects.size())).clear();}}System.out.println("线程 " + threadId + " 完成对象创建");}
}
练习步骤:
- 使用不同的GC参数运行程序
- 在VisualVM中观察GC行为
- 比较不同GC算法的性能差异
- 分析GC日志和监控数据
练习2:内存泄漏与GC压力测试
// GCPressureTest.java
import java.util.*;
import java.util.concurrent.*;public class GCPressureTest {private static final Map<String, Object> memoryLeak = new ConcurrentHashMap<>();private static final List<byte[]> largeObjects = Collections.synchronizedList(new ArrayList<>());public static void main(String[] args) throws InterruptedException {System.out.println("=== GC压力测试 ===");GCTuningHelper gcHelper = new GCTuningHelper();gcHelper.startMonitoring();// 启动内存泄漏模拟Thread leakThread = new Thread(() -> simulateMemoryLeak());leakThread.setDaemon(true);leakThread.start();// 启动大对象创建Thread largeObjectThread = new Thread(() -> createLargeObjects());largeObjectThread.setDaemon(true);largeObjectThread.start();// 定期报告GC状态for (int i = 0; i < 10; i++) {Thread.sleep(5000);System.out.println("\n--- " + (i + 1) + " 分钟后 ---");gcHelper.printGCReport();// 检查内存使用情况Runtime runtime = Runtime.getRuntime();long usedMemory = runtime.totalMemory() - runtime.freeMemory();long maxMemory = runtime.maxMemory();double memoryUsage = (double) usedMemory / maxMemory * 100;System.out.printf("内存使用率: %.2f%%\n", memoryUsage);if (memoryUsage > 80) {System.out.println("警告:内存使用率过高!");}}System.out.println("\n=== 测试完成 ===");}private static void simulateMemoryLeak() {int counter = 0;while (true) {try {// 模拟内存泄漏String key = "leak_" + counter++;memoryLeak.put(key, new byte[1024]); // 1KB对象// 偶尔清理一些,但不是全部if (counter % 1000 == 0) {Iterator<String> iterator = memoryLeak.keySet().iterator();for (int i = 0; i < 100 && iterator.hasNext(); i++) {iterator.next();iterator.remove();}}Thread.sleep(10);} catch (InterruptedException e) {break;}}}private static void createLargeObjects() {Random random = new Random();while (true) {try {// 创建大对象int size = random.nextInt(1024 * 1024) + 512 * 1024; // 0.5-1.5MBlargeObjects.add(new byte[size]);// 保持一定数量的大对象if (largeObjects.size() > 10) {largeObjects.remove(0);}Thread.sleep(1000);} catch (InterruptedException e) {break;} catch (OutOfMemoryError e) {System.out.println("内存不足,清理大对象");largeObjects.clear();}}}
}
练习3:GC调优对比
创建一个脚本来测试不同GC配置的性能:
#!/bin/bash
# gc_comparison.shecho "=== GC性能对比测试 ==="# 测试程序
TEST_CLASS="GCMonitoringExercise"
JAR_FILE="gc-test.jar"# 不同的GC配置
declare -a GC_CONFIGS=("-XX:+UseSerialGC -Xms1g -Xmx2g""-XX:+UseParallelGC -Xms1g -Xmx2g""-XX:+UseG1GC -Xms1g -Xmx2g -XX:MaxGCPauseMillis=100""-XX:+UseZGC -XX:+UnlockExperimentalVMOptions -Xms1g -Xmx2g"
)declare -a GC_NAMES=("Serial GC""Parallel GC""G1 GC""ZGC"
)# 结果文件
RESULT_FILE="gc_comparison_results.txt"
echo "GC性能对比测试结果" > $RESULT_FILE
echo "测试时间: $(date)" >> $RESULT_FILE
echo "" >> $RESULT_FILE# 运行测试
for i in "${!GC_CONFIGS[@]}"; doecho "测试 ${GC_NAMES[$i]}..."echo "=== ${GC_NAMES[$i]} ===" >> $RESULT_FILE# 运行测试并记录结果java ${GC_CONFIGS[$i]} \-XX:+PrintGC \-XX:+PrintGCDetails \-XX:+PrintGCTimeStamps \-cp $JAR_FILE $TEST_CLASS 2>&1 | tee -a $RESULT_FILEecho "" >> $RESULT_FILEecho "等待系统恢复..."sleep 5
doneecho "测试完成,结果保存在 $RESULT_FILE"
本章总结
关键要点
-
垃圾回收基础
- 理解GC的目标和挑战
- 掌握主要GC算法原理
- 了解分代垃圾回收机制
-
VisualVM GC监控
- 使用Monitor标签页监控GC活动
- 安装和使用Visual GC插件
- 解读GC性能指标
-
GC日志分析
- 配置GC日志输出
- 分析GC日志内容
- 识别GC性能问题
-
GC调优策略
- 选择合适的垃圾回收器
- 调整JVM参数
- 监控和验证调优效果
最佳实践
-
监控策略
- 建立GC性能基线
- 持续监控GC指标
- 设置合理的告警阈值
-
调优原则
- 明确性能目标(吞吐量 vs 延迟)
- 渐进式调优,避免大幅度改动
- 充分测试验证调优效果
-
性能优化
- 减少对象创建和生命周期
- 合理设置堆大小
- 选择适合的GC算法
-
故障诊断
- 分析GC日志定位问题
- 使用VisualVM进行实时监控
- 结合应用日志进行综合分析
下一章预告
下一章我们将学习应用程序性能分析,包括:
- 应用程序性能指标
- 性能瓶颈识别
- 数据库连接池监控
- 缓存性能分析
- 性能优化策略
通过前面几章的学习,我们已经掌握了VisualVM的核心功能。下一章将把这些技能应用到实际的应用程序性能分析中,帮助您成为Java性能调优专家。
1.2.2 复制算法(Copying)
// 复制算法演示
import java.util.*;public class CopyingAlgorithmDemo {private static final int HEAP_SIZE = 1000;private static Object[] fromSpace = new Object[HEAP_SIZE / 2];private static Object[] toSpace = new Object[HEAP_SIZE / 2];private static int fromPointer = 0;private static int toPointer = 0;public static void main(String[] args) {System.out.println("=== Copying Algorithm Demo ===");// 分配对象到 from 空间allocateObjects();// 执行复制 GCperformCopyingGC();System.out.println("Copying GC completed");}private static void allocateObjects() {System.out.println("Allocating objects in from-space...");// 模拟对象分配for (int i = 0; i < 200; i++) {if (fromPointer < fromSpace.length) {fromSpace[fromPointer++] = new TestObject("Object_" + i);}}System.out.println("Allocated " + fromPointer + " objects");}private static void performCopyingGC() {System.out.println("\n=== Copying GC ===");// 复制存活对象到 to 空间toPointer = 0;for (int i = 0; i < fromPointer; i++) {Object obj = fromSpace[i];if (obj != null && isAlive(obj)) {// 复制到 to 空间toSpace[toPointer++] = obj;System.out.println("Copied: " + obj);}}// 交换空间Object[] temp = fromSpace;fromSpace = toSpace;toSpace = temp;// 重置指针fromPointer = toPointer;toPointer = 0;// 清空 to 空间(原 from 空间)Arrays.fill(toSpace, null);System.out.println("Copied " + fromPointer + " live objects");System.out.println("From-space and to-space swapped");}private static boolean isAlive(Object obj) {// 简单的存活判断逻辑if (obj instanceof TestObject) {TestObject testObj = (TestObject) obj;// 假设偶数 ID 的对象存活return testObj.getId() % 2 == 0;}return false;}static class TestObject {private final String name;private final int id;public TestObject(String name) {this.name = name;this.id = Integer.parseInt(name.split("_")[1]);}public int getId() {return id;}@Overridepublic String toString() {return "TestObject{" + name + "}";}}
}
1.2.3 标记-整理算法(Mark-Compact)
// 标记-整理算法演示
import java.util.*;public class MarkCompactDemo {private static final int HEAP_SIZE = 1000;private static Object[] heap = new Object[HEAP_SIZE];private static boolean[] marked = new boolean[HEAP_SIZE];private static int heapPointer = 0;public static void main(String[] args) {System.out.println("=== Mark-Compact Algorithm Demo ===");// 分配对象allocateObjects();// 执行标记-整理 GCperformMarkCompactGC();System.out.println("Mark-Compact GC completed");}private static void allocateObjects() {System.out.println("Allocating objects...");// 分配对象,模拟内存碎片for (int i = 0; i < 300; i++) {if (heapPointer < heap.length) {heap[heapPointer++] = new CompactObject("Object_" + i);}}// 模拟一些对象变为垃圾for (int i = 0; i < heapPointer; i += 3) {heap[i] = null; // 每三个对象中删除一个}System.out.println("Initial heap state:");printHeapState();}private static void performMarkCompactGC() {System.out.println("\n=== Mark-Compact GC ===");// 标记阶段markPhase();// 整理阶段compactPhase();System.out.println("\nFinal heap state:");printHeapState();}private static void markPhase() {System.out.println("Mark phase...");Arrays.fill(marked, false);// 标记存活对象for (int i = 0; i < heapPointer; i++) {if (heap[i] != null) {marked[i] = true;}}int markedCount = 0;for (boolean mark : marked) {if (mark) markedCount++;}System.out.println("Marked " + markedCount + " objects");}private static void compactPhase() {System.out.println("Compact phase...");int writeIndex = 0;// 将存活对象移动到堆的开始位置for (int readIndex = 0; readIndex < heapPointer; readIndex++) {if (marked[readIndex] && heap[readIndex] != null) {if (readIndex != writeIndex) {heap[writeIndex] = heap[readIndex];heap[readIndex] = null;System.out.println("Moved " + heap[writeIndex] + " from " + readIndex + " to " + writeIndex);}writeIndex++;}}// 清空剩余空间for (int i = writeIndex; i < heapPointer; i++) {heap[i] = null;}heapPointer = writeIndex;System.out.println("Compacted to " + heapPointer + " objects");}private static void printHeapState() {System.out.print("Heap: [");for (int i = 0; i < Math.min(20, heap.length); i++) {if (heap[i] != null) {System.out.print("O");} else {System.out.print(".");}}System.out.println("] (showing first 20 slots)");int liveObjects = 0;for (Object obj : heap) {if (obj != null) liveObjects++;}System.out.println("Live objects: " + liveObjects + "/" + heapPointer);}static class CompactObject {private final String name;public CompactObject(String name) {this.name = name;}@Overridepublic String toString() {return name;}}
}
1.3 分代垃圾回收
1.3.1 分代假设
分代垃圾回收基于以下观察:
- 弱分代假设:大多数对象很快变为垃圾
- 强分代假设:存活时间长的对象倾向于继续存活
- 跨代引用稀少:老年代对象很少引用新生代对象
1.3.2 分代结构
// 分代垃圾回收演示
import java.util.*;
import java.util.concurrent.*;public class GenerationalGCDemo {// 模拟分代结构private static final YoungGeneration youngGen = new YoungGeneration();private static final OldGeneration oldGen = new OldGeneration();private static final List<Object> roots = new ArrayList<>();public static void main(String[] args) throws InterruptedException {System.out.println("=== Generational GC Demo ===");// 模拟对象分配和 GCsimulateAllocation();System.out.println("Generational GC simulation completed");}private static void simulateAllocation() throws InterruptedException {Random random = new Random();for (int cycle = 0; cycle < 10; cycle++) {System.out.println("\n=== Allocation Cycle " + (cycle + 1) + " ===");// 分配短生命周期对象for (int i = 0; i < 100; i++) {GenerationalObject obj = new GenerationalObject("Short_" + cycle + "_" + i, ObjectType.SHORT_LIVED);youngGen.allocate(obj);// 10% 的对象成为根对象if (random.nextDouble() < 0.1) {roots.add(obj);}}// 分配中等生命周期对象for (int i = 0; i < 20; i++) {GenerationalObject obj = new GenerationalObject("Medium_" + cycle + "_" + i, ObjectType.MEDIUM_LIVED);youngGen.allocate(obj);if (random.nextDouble() < 0.3) {roots.add(obj);}}// 分配长生命周期对象for (int i = 0; i < 5; i++) {GenerationalObject obj = new GenerationalObject("Long_" + cycle + "_" + i, ObjectType.LONG_LIVED);youngGen.allocate(obj);roots.add(obj); // 长生命周期对象都是根对象}// 检查是否需要 Minor GCif (youngGen.needsGC()) {performMinorGC();}// 检查是否需要 Major GCif (oldGen.needsGC()) {performMajorGC();}// 模拟一些根对象失效if (cycle > 2) {removeOldRoots(random);}Thread.sleep(100); // 模拟时间流逝}}private static void performMinorGC() {System.out.println("\n--- Minor GC ---");Set<GenerationalObject> survivors = new HashSet<>();// 从根对象标记年轻代中的存活对象for (Object root : roots) {if (root instanceof GenerationalObject) {GenerationalObject obj = (GenerationalObject) root;if (youngGen.contains(obj)) {markSurvivors(obj, survivors);}}}// 处理存活对象List<GenerationalObject> promoted = new ArrayList<>();for (GenerationalObject survivor : survivors) {survivor.incrementAge();// 年龄达到阈值的对象晋升到老年代if (survivor.getAge() >= 3) {promoted.add(survivor);}}// 晋升对象到老年代for (GenerationalObject obj : promoted) {youngGen.remove(obj);oldGen.add(obj);System.out.println("Promoted to old generation: " + obj);}// 清理年轻代youngGen.clear();// 将剩余存活对象放回年轻代for (GenerationalObject survivor : survivors) {if (!promoted.contains(survivor)) {youngGen.allocate(survivor);}}System.out.println("Minor GC completed. Promoted: " + promoted.size() + ", Survivors: " + (survivors.size() - promoted.size()));}private static void performMajorGC() {System.out.println("\n--- Major GC ---");Set<GenerationalObject> allSurvivors = new HashSet<>();// 标记所有代中的存活对象for (Object root : roots) {if (root instanceof GenerationalObject) {markSurvivors((GenerationalObject) root, allSurvivors);}}// 清理老年代oldGen.retainAll(allSurvivors);// 清理年轻代youngGen.clear();for (GenerationalObject survivor : allSurvivors) {if (!oldGen.contains(survivor)) {youngGen.allocate(survivor);}}System.out.println("Major GC completed. Total survivors: " + allSurvivors.size());}private static void markSurvivors(GenerationalObject obj, Set<GenerationalObject> survivors) {if (obj == null || survivors.contains(obj)) {return;}survivors.add(obj);// 递归标记引用的对象for (GenerationalObject ref : obj.getReferences()) {markSurvivors(ref, survivors);}}private static void removeOldRoots(Random random) {// 移除一些旧的根对象Iterator<Object> iterator = roots.iterator();while (iterator.hasNext()) {Object root = iterator.next();if (root instanceof GenerationalObject) {GenerationalObject obj = (GenerationalObject) root;// 根据对象类型决定移除概率double removeProb = switch (obj.getType()) {case SHORT_LIVED -> 0.8;case MEDIUM_LIVED -> 0.3;case LONG_LIVED -> 0.05;};if (random.nextDouble() < removeProb) {iterator.remove();}}}}// 年轻代实现static class YoungGeneration {private final List<GenerationalObject> objects = new ArrayList<>();private static final int MAX_SIZE = 200;public void allocate(GenerationalObject obj) {objects.add(obj);}public boolean needsGC() {return objects.size() > MAX_SIZE;}public boolean contains(GenerationalObject obj) {return objects.contains(obj);}public void remove(GenerationalObject obj) {objects.remove(obj);}public void clear() {objects.clear();}public int size() {return objects.size();}}// 老年代实现static class OldGeneration {private final Set<GenerationalObject> objects = new HashSet<>();private static final int MAX_SIZE = 500;public void add(GenerationalObject obj) {objects.add(obj);}public boolean needsGC() {return objects.size() > MAX_SIZE;}public boolean contains(GenerationalObject obj) {return objects.contains(obj);}public void retainAll(Set<GenerationalObject> survivors) {objects.retainAll(survivors);}public int size() {return objects.size();}}// 对象类型枚举enum ObjectType {SHORT_LIVED, MEDIUM_LIVED, LONG_LIVED}// 分代对象static class GenerationalObject {private final String name;private final ObjectType type;private int age = 0;private final List<GenerationalObject> references = new ArrayList<>();public GenerationalObject(String name, ObjectType type) {this.name = name;this.type = type;}public void incrementAge() {age++;}public int getAge() {return age;}public ObjectType getType() {return type;}public List<GenerationalObject> getReferences() {return references;}public void addReference(GenerationalObject obj) {references.add(obj);}@Overridepublic String toString() {return name + "(age=" + age + ", type=" + type + ")";}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (!(o instanceof GenerationalObject)) return false;GenerationalObject that = (GenerationalObject) o;return Objects.equals(name, that.name);}@Overridepublic int hashCode() {return Objects.hash(name);}}
}
2. VisualVM 垃圾回收监控
2.1 GC 监控界面
2.1.1 Monitor 标签页的 GC 信息
VisualVM 的 Monitor 标签页提供了实时的垃圾回收监控信息:
- Heap Size:堆内存大小变化
- Used Heap:已使用堆内存
- GC Activity:垃圾回收活动图表
- Generations:分代内存使用情况
2.1.2 GC 性能指标
// GC 监控示例程序
import java.lang.management.*;
import java.util.*;
import java.util.concurrent.*;public class GCMonitoringExample {private static final List<Object> memoryConsumers = new ArrayList<>();private static final Random random = new Random();public static void main(String[] args) throws InterruptedException {System.out.println("=== GC Monitoring Example ===");System.out.println("PID: " + ProcessHandle.current().pid());System.out.println("Monitor this process with VisualVM");// 启动 GC 监控startGCMonitoring();// 创建不同的内存使用模式simulateMemoryPatterns();}private static void startGCMonitoring() {ScheduledExecutorService monitor = Executors.newSingleThreadScheduledExecutor();monitor.scheduleAtFixedRate(() -> {printGCStats();}, 0, 5, TimeUnit.SECONDS);}private static void printGCStats() {MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();List<GarbageCollectorMXBean> gcBeans = ManagementFactory.getGarbageCollectorMXBeans();MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();System.out.println("\n=== GC Statistics ===");System.out.printf("Heap: %d MB / %d MB (%.1f%%)%n",heapUsage.getUsed() / 1024 / 1024,heapUsage.getMax() / 1024 / 1024,(double) heapUsage.getUsed() / heapUsage.getMax() * 100);for (GarbageCollectorMXBean gcBean : gcBeans) {System.out.printf("%s: %d collections, %d ms total%n",gcBean.getName(),gcBean.getCollectionCount(),gcBean.getCollectionTime());}}private static void simulateMemoryPatterns() throws InterruptedException {ExecutorService executor = Executors.newFixedThreadPool(3);// 模式 1:频繁的小对象分配executor.submit(() -> {try {frequentSmallAllocations();} catch (InterruptedException e) {Thread.currentThread().interrupt();}});// 模式 2:大对象分配executor.submit(() -> {try {largeObjectAllocations();} catch (InterruptedException e) {Thread.currentThread().interrupt();}});// 模式 3:内存泄漏模拟executor.submit(() -> {try {memoryLeakSimulation();} catch (InterruptedException e) {Thread.currentThread().interrupt();}});// 运行 10 分钟Thread.sleep(600_000);executor.shutdownNow();System.out.println("Memory pattern simulation completed");}private static void frequentSmallAllocations() throws InterruptedException {while (!Thread.currentThread().isInterrupted()) {// 创建大量小对象List<String> tempList = new ArrayList<>();for (int i = 0; i < 1000; i++) {tempList.add("TempString_" + i + "_" + System.nanoTime());}// 偶尔保留一些对象if (random.nextDouble() < 0.1) {memoryConsumers.add(tempList);}Thread.sleep(10);}}private static void largeObjectAllocations() throws InterruptedException {while (!Thread.currentThread().isInterrupted()) {// 创建大对象byte[] largeArray = new byte[1024 * 1024]; // 1MBArrays.fill(largeArray, (byte) random.nextInt(256));// 偶尔保留大对象if (random.nextDouble() < 0.05) {memoryConsumers.add(largeArray);}Thread.sleep(500);}}private static void memoryLeakSimulation() throws InterruptedException {Map<String, Object> leakyMap = new HashMap<>();int counter = 0;while (!Thread.currentThread().isInterrupted()) {// 模拟内存泄漏:不断添加对象但很少移除String key = "leak_" + (counter++);Object value = new LargeObject(key);leakyMap.put(key, value);// 偶尔清理一些旧对象if (counter % 1000 == 0) {Iterator<String> iterator = leakyMap.keySet().iterator();for (int i = 0; i < 100 && iterator.hasNext(); i++) {iterator.next();iterator.remove();}}Thread.sleep(50);}}static class LargeObject {private final String id;private final byte[] data;private final List<String> metadata;public LargeObject(String id) {this.id = id;this.data = new byte[10240]; // 10KBthis.metadata = new ArrayList<>();// 填充数据Arrays.fill(data, (byte) id.hashCode());// 添加元数据for (int i = 0; i < 100; i++) {metadata.add("metadata_" + id + "_" + i);}}public String getId() {return id;}@Overridepublic String toString() {return "LargeObject{id='" + id + "', size=" + data.length + "}";}}
}
2.2 Visual GC 插件
2.2.1 安装 Visual GC 插件
- 打开 VisualVM
- 选择 Tools → Plugins
- 在 Available Plugins 中找到 Visual GC
- 点击 Install 安装插件
- 重启 VisualVM
2.2.2 Visual GC 界面解读
Visual GC 插件提供了详细的垃圾回收可视化信息:
- Spaces:各个内存区域的使用情况
- Graphs:实时的内存使用图表
- Histogram:对象年龄分布
- Details:详细的 GC 统计信息
// Visual GC 演示程序
import java.util.*;
import java.util.concurrent.*;public class VisualGCDemo {private static final List<Object> youngObjects = new ArrayList<>();private static final List<Object> oldObjects = new ArrayList<>();private static final Random random = new Random();public static void main(String[] args) throws InterruptedException {System.out.println("=== Visual GC Demo ===");System.out.println("PID: " + ProcessHandle.current().pid());System.out.println("Use VisualVM with Visual GC plugin to monitor");// 创建不同生命周期的对象createGenerationalObjects();System.out.println("Visual GC demo completed");}private static void createGenerationalObjects() throws InterruptedException {ExecutorService executor = Executors.newFixedThreadPool(2);// 创建短生命周期对象(主要在年轻代)executor.submit(() -> {try {createYoungGenerationObjects();} catch (InterruptedException e) {Thread.currentThread().interrupt();}});// 创建长生命周期对象(会晋升到老年代)executor.submit(() -> {try {createOldGenerationObjects();} catch (InterruptedException e) {Thread.currentThread().interrupt();}});// 运行 5 分钟Thread.sleep(300_000);executor.shutdownNow();}private static void createYoungGenerationObjects() throws InterruptedException {while (!Thread.currentThread().isInterrupted()) {// 创建大量短生命周期对象for (int i = 0; i < 1000; i++) {YoungObject obj = new YoungObject("young_" + i);youngObjects.add(obj);// 快速释放大部分对象if (youngObjects.size() > 5000) {youngObjects.subList(0, 4000).clear();}}Thread.sleep(100);}}private static void createOldGenerationObjects() throws InterruptedException {while (!Thread.currentThread().isInterrupted()) {// 创建长生命周期对象OldObject obj = new OldObject("old_" + System.currentTimeMillis());oldObjects.add(obj);// 偶尔清理一些旧对象if (oldObjects.size() > 1000 && random.nextDouble() < 0.1) {oldObjects.remove(0);}Thread.sleep(1000);}}static class YoungObject {private final String name;private final byte[] data;public YoungObject(String name) {this.name = name;this.data = new byte[1024]; // 1KB}@Overridepublic String toString() {return "YoungObject{" + name + "}";}}static class OldObject {private final String name;private final byte[] data;private final List<String> references;public OldObject(String name) {this.name = name;this.data = new byte[10240]; // 10KBthis.references = new ArrayList<>();// 添加一些引用for (int i = 0; i < 50; i++) {references.add("ref_" + name + "_" + i);}}@Overridepublic String toString() {return "OldObject{" + name + "}";}}
}
3. GC 日志分析
3.1 启用 GC 日志
3.1.1 JVM 参数配置
# Java 8 及之前版本
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintGCDateStamps
-Xloggc:gc.log# Java 9 及之后版本
-Xlog:gc*:gc.log:time,tags
3.1.2 GC 日志示例程序
// GC 日志生成程序
import java.util.*;
import java.util.concurrent.*;public class GCLogGenerator {private static final List<Object> memoryHolder = new ArrayList<>();private static final Random random = new Random();public static void main(String[] args) throws InterruptedException {System.out.println("=== GC Log Generator ===");System.out.println("Run with GC logging enabled:");System.out.println("Java 8: -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log");System.out.println("Java 11+: -Xlog:gc*:gc.log:time,tags");// 生成不同类型的 GC 事件generateGCEvents();System.out.println("GC log generation completed");}private static void generateGCEvents() throws InterruptedException {// 阶段 1:触发 Minor GCSystem.out.println("\nPhase 1: Triggering Minor GC...");triggerMinorGC();Thread.sleep(2000);// 阶段 2:触发 Major GCSystem.out.println("\nPhase 2: Triggering Major GC...");triggerMajorGC();Thread.sleep(2000);// 阶段 3:内存压力测试System.out.println("\nPhase 3: Memory pressure test...");memoryPressureTest();Thread.sleep(2000);// 阶段 4:清理内存System.out.println("\nPhase 4: Memory cleanup...");memoryHolder.clear();System.gc(); // 建议进行垃圾回收}private static void triggerMinorGC() throws InterruptedException {// 快速分配大量小对象,触发 Minor GCfor (int i = 0; i < 10; i++) {List<String> tempObjects = new ArrayList<>();for (int j = 0; j < 100000; j++) {tempObjects.add("MinorGC_" + i + "_" + j);}// 保留少量对象if (i % 3 == 0) {memoryHolder.add(tempObjects.subList(0, 1000));}Thread.sleep(100);}}private static void triggerMajorGC() throws InterruptedException {// 分配大量长生命周期对象,触发 Major GCfor (int i = 0; i < 100; i++) {LongLivedObject obj = new LongLivedObject("MajorGC_" + i);memoryHolder.add(obj);// 创建一些临时的大对象byte[] largeArray = new byte[1024 * 1024]; // 1MBArrays.fill(largeArray, (byte) i);Thread.sleep(50);}}private static void memoryPressureTest() throws InterruptedException {// 创建内存压力,观察 GC 行为ExecutorService executor = Executors.newFixedThreadPool(4);for (int i = 0; i < 4; i++) {final int threadId = i;executor.submit(() -> {try {createMemoryPressure(threadId);} catch (InterruptedException e) {Thread.currentThread().interrupt();}});}Thread.sleep(30000); // 运行 30 秒executor.shutdownNow();}private static void createMemoryPressure(int threadId) throws InterruptedException {List<Object> localObjects = new ArrayList<>();while (!Thread.currentThread().isInterrupted()) {// 分配不同大小的对象for (int i = 0; i < 100; i++) {Object obj;if (random.nextDouble() < 0.7) {// 70% 小对象obj = new SmallObject("thread_" + threadId + "_small_" + i);} else if (random.nextDouble() < 0.9) {// 20% 中等对象obj = new MediumObject("thread_" + threadId + "_medium_" + i);} else {// 10% 大对象obj = new LargeObject("thread_" + threadId + "_large_" + i);}localObjects.add(obj);}// 随机清理一些对象if (localObjects.size() > 1000) {int removeCount = random.nextInt(500);for (int i = 0; i < removeCount && !localObjects.isEmpty(); i++) {localObjects.remove(random.nextInt(localObjects.size()));}}Thread.sleep(10);}}static class SmallObject {private final String name;private final int[] data = new int[10]; // 40 bytespublic SmallObject(String name) {this.name = name;Arrays.fill(data, name.hashCode());}}static class MediumObject {private final String name;private final byte[] data = new byte[1024]; // 1KBpublic MediumObject(String name) {this.name = name;Arrays.fill(data, (byte) name.hashCode());}}static class LargeObject {private final String name;private final byte[] data = new byte[10240]; // 10KBpublic LargeObject(String name) {this.name = name;Arrays.fill(data, (byte) name.hashCode());}}static class LongLivedObject {private final String name;private final Map<String, Object> properties = new HashMap<>();private final List<String> history = new ArrayList<>();public LongLivedObject(String name) {this.name = name;// 添加属性for (int i = 0; i < 50; i++) {properties.put("prop_" + i, "value_" + i + "_" + name);}// 添加历史记录for (int i = 0; i < 100; i++) {history.add("event_" + i + "_" + System.currentTimeMillis());}}@Overridepublic String toString() {return "LongLivedObject{" + name + "}";}}
}
3.2 GC 日志解读
3.2.1 日志格式解析
// GC 日志解析工具
import java.io.*;
import java.nio.file.*;
import java.time.*;
import java.time.format.*;
import java.util.*;
import java.util.regex.*;public class GCLogAnalyzer {private static final Pattern GC_PATTERN = Pattern.compile("(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}[+-]\\d{4}): " +"\\[(\\d+\\.\\d+)s\\]\\[info\\]\\[gc\\s*\\] " +"GC\\((\\d+)\\) (.+)");private static final Pattern PAUSE_PATTERN = Pattern.compile("Pause (.+?) (\\d+)M->(\\d+)M\\((\\d+)M\\) (\\d+\\.\\d+)ms");public static void main(String[] args) {if (args.length != 1) {System.out.println("Usage: java GCLogAnalyzer <gc-log-file>");return;}String logFile = args[0];try {analyzeGCLog(logFile);} catch (IOException e) {System.err.println("Error reading GC log: " + e.getMessage());}}private static void analyzeGCLog(String logFile) throws IOException {System.out.println("=== GC Log Analysis ===");System.out.println("Analyzing: " + logFile);List<GCEvent> events = parseGCLog(logFile);if (events.isEmpty()) {System.out.println("No GC events found in log file");return;}// 分析统计信息analyzeStatistics(events);// 分析趋势analyzeTrends(events);// 识别问题identifyIssues(events);}private static List<GCEvent> parseGCLog(String logFile) throws IOException {List<GCEvent> events = new ArrayList<>();List<String> lines = Files.readAllLines(Paths.get(logFile));for (String line : lines) {GCEvent event = parseGCEvent(line);if (event != null) {events.add(event);}}System.out.println("Parsed " + events.size() + " GC events");return events;}private static GCEvent parseGCEvent(String line) {Matcher gcMatcher = GC_PATTERN.matcher(line);if (!gcMatcher.find()) {return null;}String timestamp = gcMatcher.group(1);double uptime = Double.parseDouble(gcMatcher.group(2));int gcId = Integer.parseInt(gcMatcher.group(3));String details = gcMatcher.group(4);Matcher pauseMatcher = PAUSE_PATTERN.matcher(details);if (pauseMatcher.find()) {String gcType = pauseMatcher.group(1);int beforeMB = Integer.parseInt(pauseMatcher.group(2));int afterMB = Integer.parseInt(pauseMatcher.group(3));int totalMB = Integer.parseInt(pauseMatcher.group(4));double pauseMs = Double.parseDouble(pauseMatcher.group(5));return new GCEvent(timestamp, uptime, gcId, gcType, beforeMB, afterMB, totalMB, pauseMs);}return null;}private static void analyzeStatistics(List<GCEvent> events) {System.out.println("\n=== GC Statistics ===");Map<String, List<GCEvent>> eventsByType = new HashMap<>();for (GCEvent event : events) {eventsByType.computeIfAbsent(event.getType(), k -> new ArrayList<>()).add(event);}for (Map.Entry<String, List<GCEvent>> entry : eventsByType.entrySet()) {String type = entry.getKey();List<GCEvent> typeEvents = entry.getValue();double totalPause = typeEvents.stream().mapToDouble(GCEvent::getPauseMs).sum();double avgPause = totalPause / typeEvents.size();double maxPause = typeEvents.stream().mapToDouble(GCEvent::getPauseMs).max().orElse(0);double minPause = typeEvents.stream().mapToDouble(GCEvent::getPauseMs).min().orElse(0);System.out.printf("%s GC:%n", type);System.out.printf(" Count: %d%n", typeEvents.size());System.out.printf(" Total pause: %.2f ms%n", totalPause);System.out.printf(" Average pause: %.2f ms%n", avgPause);System.out.printf(" Max pause: %.2f ms%n", maxPause);System.out.printf(" Min pause: %.2f ms%n", minPause);System.out.println();}}private static void analyzeTrends(List<GCEvent> events) {System.out.println("=== GC Trends ===");if (events.size() < 10) {System.out.println("Not enough events for trend analysis");return;}// 分析暂停时间趋势List<Double> pauseTimes = events.stream().map(GCEvent::getPauseMs).toList();double trend = calculateTrend(pauseTimes);if (trend > 0.1) {System.out.println("⚠️ GC pause times are increasing (trend: +" + String.format("%.2f", trend) + " ms per GC)");} else if (trend < -0.1) {System.out.println("✅ GC pause times are decreasing (trend: " + String.format("%.2f", trend) + " ms per GC)");} else {System.out.println("➡️ GC pause times are stable");}// 分析内存使用趋势List<Double> memoryUsage = events.stream().map(event -> (double) event.getAfterMB() / event.getTotalMB()).toList();double memoryTrend = calculateTrend(memoryUsage);if (memoryTrend > 0.01) {System.out.println("⚠️ Memory usage is increasing (possible memory leak)");} else {System.out.println("✅ Memory usage is stable");}}private static double calculateTrend(List<Double> values) {if (values.size() < 2) return 0;double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;int n = values.size();for (int i = 0; i < n; i++) {double x = i;double y = values.get(i);sumX += x;sumY += y;sumXY += x * y;sumX2 += x * x;}return (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);}private static void identifyIssues(List<GCEvent> events) {System.out.println("\n=== Issue Detection ===");// 检查长暂停时间List<GCEvent> longPauses = events.stream().filter(event -> event.getPauseMs() > 100) // 超过 100ms.toList();if (!longPauses.isEmpty()) {System.out.println("⚠️ Found " + longPauses.size() + " GC events with pause > 100ms:");longPauses.forEach(event -> System.out.printf(" %s: %.2f ms%n", event.getType(), event.getPauseMs()));}// 检查频繁 GCdouble avgInterval = calculateAverageInterval(events);if (avgInterval < 1.0) { // 平均间隔小于 1 秒System.out.println("⚠️ Frequent GC detected (average interval: " + String.format("%.2f", avgInterval) + " seconds)");}// 检查内存回收效率double avgReclaimed = events.stream().mapToDouble(event -> (double)(event.getBeforeMB() - event.getAfterMB()) / event.getBeforeMB()).average().orElse(0);if (avgReclaimed < 0.1) { // 平均回收率小于 10%System.out.println("⚠️ Low GC efficiency detected (average reclaimed: " + String.format("%.1f%%", avgReclaimed * 100) + ")");}if (longPauses.isEmpty() && avgInterval >= 1.0 && avgReclaimed >= 0.1) {System.out.println("✅ No significant GC issues detected");}}private static double calculateAverageInterval(List<GCEvent> events) {if (events.size() < 2) return Double.MAX_VALUE;double totalInterval = 0;for (int i = 1; i < events.size(); i++) {totalInterval += events.get(i).getUptime() - events.get(i-1).getUptime();}return totalInterval / (events.size() - 1);}static class GCEvent {private final String timestamp;private final double uptime;private final int gcId;private final String type;private final int beforeMB;private final int afterMB;private final int totalMB;private final double pauseMs;public GCEvent(String timestamp, double uptime, int gcId, String type,int beforeMB, int afterMB, int totalMB, double pauseMs) {this.timestamp = timestamp;this.uptime = uptime;this.gcId = gcId;this.type = type;this.beforeMB = beforeMB;this.afterMB = afterMB;this.totalMB = totalMB;this.pauseMs = pauseMs;}// Getterspublic String getTimestamp() { return timestamp; }public double getUptime() { return uptime; }public int getGcId() { return gcId; }public String getType() { return type; }public int getBeforeMB() { return beforeMB; }public int getAfterMB() { return afterMB; }public int getTotalMB() { return totalMB; }public double getPauseMs() { return pauseMs; }@Overridepublic String toString() {return String.format("GC(%d) %s: %dM->%dM(%dM) %.2fms", gcId, type, beforeMB, afterMB, totalMB, pauseMs);}}
}
4. 垃圾回收器选择与调优
4.1 垃圾回收器对比
4.1.1 Serial GC
// Serial GC 演示
// JVM 参数: -XX:+UseSerialGC -Xmx512m -Xms512mimport java.util.*;
import java.util.concurrent.*;public class SerialGCDemo {public static void main(String[] args) throws InterruptedException {System.out.println("=== Serial GC Demo ===");System.out.println("Run with: -XX:+UseSerialGC -Xmx512m -Xms512m");