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");