前言
Java 虛擬機器(JVM)的記憶體管理是 Java 應用程式效能的核心關鍵。本文將深入探討 JVM 記憶體結構,特別是堆疊(Stack)與堆積(Heap)記憶體的運作機制,並提供實際的程式碼範例與最佳化策略。
JVM 記憶體結構概覽
JVM 記憶體主要分為以下幾個區域:
graph TB
subgraph "JVM 記憶體區域"
PC[程式計數器<br/>Program Counter]
STACK[虛擬機器堆疊<br/>VM Stack]
NATIVE[本地方法堆疊<br/>Native Method Stack]
HEAP[堆積記憶體<br/>Heap Memory]
METHOD[方法區<br/>Method Area]
DIRECT[直接記憶體<br/>Direct Memory]
end
subgraph "堆積記憶體詳細結構"
YOUNG[年輕世代<br/>Young Generation]
OLD[老年世代<br/>Old Generation]
subgraph "年輕世代細分"
EDEN[Eden 區]
S0[Survivor 0]
S1[Survivor 1]
end
end
HEAP --> YOUNG
HEAP --> OLD
YOUNG --> EDEN
YOUNG --> S0
YOUNG --> S1
JVM 記憶體區域比較表
記憶體區域 | 執行緒共享 | 生命週期 | 主要用途 | GC 影響 |
---|---|---|---|---|
程式計數器 | 否 | 執行緒生命週期 | 記錄當前執行指令位置 | 無 |
虛擬機器堆疊 | 否 | 執行緒生命週期 | 方法調用與局部變數 | 無 |
本地方法堆疊 | 否 | 執行緒生命週期 | 本地方法調用 | 無 |
堆積記憶體 | 是 | JVM 生命週期 | 物件實例與陣列 | 是 |
方法區 | 是 | JVM 生命週期 | 類別資訊與常數池 | 部分 |
直接記憶體 | 是 | 手動管理 | NIO 操作緩衝區 | 無 |
堆疊記憶體(Stack Memory)深度解析
堆疊記憶體的特性
堆疊記憶體是每個執行緒獨有的記憶體區域,採用 LIFO(Last In First Out)的結構。
1public class StackMemoryDemo {
2
3 public static void main(String[] args) {
4 // 在 main 方法的堆疊框架中
5 int mainVariable = 10;
6 System.out.println("主方法開始執行,堆疊深度:1");
7
8 // 調用方法,增加堆疊深度
9 methodA(mainVariable);
10
11 System.out.println("主方法執行完畢");
12 }
13
14 public static void methodA(int parameter) {
15 // 在 methodA 的堆疊框架中
16 int localVariableA = 20;
17 System.out.println("方法 A 執行,堆疊深度:2,參數值:" + parameter);
18
19 // 再次調用方法,進一步增加堆疊深度
20 methodB(localVariableA + parameter);
21 }
22
23 public static void methodB(int parameter) {
24 // 在 methodB 的堆疊框架中
25 int localVariableB = 30;
26 System.out.println("方法 B 執行,堆疊深度:3,計算結果:" +
27 (localVariableB + parameter));
28
29 // 方法結束時,堆疊框架被移除
30 System.out.println("方法 B 執行完畢,準備返回");
31 }
32}
堆疊框架(Stack Frame)結構
每個方法調用都會在堆疊中建立一個框架:
1@Component
2public class StackFrameAnalyzer {
3
4 public void demonstrateStackFrame() {
5 // 1. 局部變數表(Local Variable Table)
6 int intValue = 100; // slot 0
7 long longValue = 200L; // slot 1-2 (long 佔用兩個 slot)
8 String stringValue = "測試"; // slot 3
9 Object objValue = new Object(); // slot 4
10
11 // 2. 運算元堆疊(Operand Stack)
12 int result = calculateSum(intValue, (int)longValue);
13
14 // 3. 動態連結(Dynamic Linking)
15 System.out.println("計算結果:" + result);
16
17 // 4. 方法返回位址(Return Address)
18 // 當方法正常返回或異常返回時使用
19 }
20
21 private int calculateSum(int a, int b) {
22 // 新的堆疊框架被創建
23 int sum = a + b;
24
25 // 運算元堆疊操作示例:
26 // 1. 載入 a 到運算元堆疊
27 // 2. 載入 b 到運算元堆疊
28 // 3. 執行 iadd 指令
29 // 4. 將結果存儲到 sum 變數
30
31 return sum; // 返回值通過運算元堆疊傳遞
32 }
33}
堆疊記憶體配置與調優
1@Configuration
2public class StackMemoryConfiguration {
3
4 // JVM 參數設定範例
5 // -Xss1m:設定每個執行緒的堆疊大小為 1MB
6 // -XX:+PrintGCDetails:印出 GC 詳細資訊
7
8 @Bean
9 public ThreadPoolExecutor customThreadPool() {
10 return new ThreadPoolExecutor(
11 5, // 核心執行緒數
12 10, // 最大執行緒數
13 60L, TimeUnit.SECONDS, // 保持存活時間
14 new LinkedBlockingQueue<>(100), // 工作佇列
15 new ThreadFactory() {
16 private AtomicInteger threadNumber = new AtomicInteger(1);
17
18 @Override
19 public Thread newThread(Runnable r) {
20 Thread thread = new Thread(r, "CustomThread-" +
21 threadNumber.getAndIncrement());
22
23 // 監控執行緒堆疊使用情況
24 thread.setUncaughtExceptionHandler((t, e) -> {
25 if (e instanceof StackOverflowError) {
26 log.error("執行緒 {} 發生堆疊溢位:{}", t.getName(), e.getMessage());
27 }
28 });
29
30 return thread;
31 }
32 }
33 );
34 }
35
36 @Component
37 public static class StackMonitor {
38
39 public void analyzeStackUsage() {
40 ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
41
42 for (ThreadInfo threadInfo : threadBean.dumpAllThreads(false, false)) {
43 StackTraceElement[] stackTrace = threadInfo.getStackTrace();
44
45 System.out.printf("執行緒 %s 堆疊深度:%d%n",
46 threadInfo.getThreadName(),
47 stackTrace.length);
48
49 // 分析堆疊使用情況
50 if (stackTrace.length > 100) {
51 System.out.printf("警告:執行緒 %s 堆疊深度過深!%n",
52 threadInfo.getThreadName());
53 }
54 }
55 }
56 }
57}
堆積記憶體(Heap Memory)深度解析
堆積記憶體結構與分代
1public class HeapMemoryDemo {
2
3 // 類別變數存儲在方法區,物件實例存儲在堆積
4 private static List<String> staticList = new ArrayList<>();
5 private List<Integer> instanceList = new ArrayList<>();
6
7 public void demonstrateHeapMemory() {
8
9 // 1. Eden 區:新物件分配
10 String newString = new String("新建字串"); // 分配到 Eden 區
11 StringBuilder sb = new StringBuilder(); // 分配到 Eden 區
12
13 // 2. 短生命週期物件(很快成為垃圾)
14 for (int i = 0; i < 1000; i++) {
15 String temp = "臨時字串" + i; // 這些物件很快就會被 GC
16 }
17
18 // 3. 長生命週期物件(可能晉升到老年世代)
19 instanceList.add(100);
20 staticList.add("持久字串");
21
22 // 4. 大物件(可能直接分配到老年世代)
23 int[] largeArray = new int[1000000]; // 大陣列可能直接進入老年世代
24
25 demonstrateObjectLifecycle();
26 }
27
28 private void demonstrateObjectLifecycle() {
29 // 物件生命週期追蹤
30 Object youngObject = new Object(); // Eden 區
31
32 // 觸發 Minor GC
33 System.gc(); // 手動建議 GC(實際執行時機由 JVM 決定)
34
35 // 長期持有的引用,防止被回收
36 staticList.add(youngObject.toString());
37 }
38}
垃圾回收機制詳解
1@Component
2public class GarbageCollectionAnalyzer {
3
4 private final List<MemoryPoolMXBean> memoryPools;
5 private final GarbageCollectorMXBean youngGenGC;
6 private final GarbageCollectorMXBean oldGenGC;
7
8 public GarbageCollectionAnalyzer() {
9 this.memoryPools = ManagementFactory.getMemoryPoolMXBeans();
10
11 List<GarbageCollectorMXBean> gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
12 this.youngGenGC = findGCBean(gcBeans, "PS Scavenge", "G1 Young Generation");
13 this.oldGenGC = findGCBean(gcBeans, "PS MarkSweep", "G1 Old Generation");
14 }
15
16 public GCAnalysisResult analyzeGCPerformance() {
17
18 // 年輕世代記憶體分析
19 MemoryUsage edenUsage = getMemoryPoolUsage("Eden Space", "G1 Eden Space");
20 MemoryUsage survivor0Usage = getMemoryPoolUsage("Survivor Space", "G1 Survivor Space");
21 MemoryUsage oldGenUsage = getMemoryPoolUsage("Old Gen", "G1 Old Gen");
22
23 // GC 統計資訊
24 long youngGCCount = youngGenGC != null ? youngGenGC.getCollectionCount() : 0;
25 long youngGCTime = youngGenGC != null ? youngGenGC.getCollectionTime() : 0;
26 long oldGCCount = oldGenGC != null ? oldGenGC.getCollectionCount() : 0;
27 long oldGCTime = oldGenGC != null ? oldGenGC.getCollectionTime() : 0;
28
29 return GCAnalysisResult.builder()
30 .edenSpaceUsage(calculateUsagePercentage(edenUsage))
31 .survivorSpaceUsage(calculateUsagePercentage(survivor0Usage))
32 .oldGenUsage(calculateUsagePercentage(oldGenUsage))
33 .youngGCCount(youngGCCount)
34 .youngGCTime(youngGCTime)
35 .oldGCCount(oldGCCount)
36 .oldGCTime(oldGCTime)
37 .avgYoungGCTime(youngGCCount > 0 ? (double)youngGCTime / youngGCCount : 0)
38 .avgOldGCTime(oldGCCount > 0 ? (double)oldGCTime / oldGCCount : 0)
39 .build();
40 }
41
42 @Scheduled(fixedRate = 60000) // 每分鐘執行一次
43 public void monitorMemoryUsage() {
44 GCAnalysisResult result = analyzeGCPerformance();
45
46 log.info("=== JVM 記憶體使用情況 ===");
47 log.info("Eden 區使用率:{:.2f}%", result.getEdenSpaceUsage());
48 log.info("Survivor 區使用率:{:.2f}%", result.getSurvivorSpaceUsage());
49 log.info("老年世代使用率:{:.2f}%", result.getOldGenUsage());
50 log.info("年輕世代 GC 次數:{},平均時間:{:.2f}ms",
51 result.getYoungGCCount(), result.getAvgYoungGCTime());
52 log.info("老年世代 GC 次數:{},平均時間:{:.2f}ms",
53 result.getOldGCCount(), result.getAvgOldGCTime());
54
55 // 記憶體使用率過高時發出警告
56 if (result.getOldGenUsage() > 80.0) {
57 log.warn("警告:老年世代記憶體使用率過高!建議調整 JVM 參數或最佳化程式碼");
58 }
59 }
60
61 private double calculateUsagePercentage(MemoryUsage usage) {
62 if (usage == null || usage.getMax() == -1) return 0.0;
63 return (double) usage.getUsed() / usage.getMax() * 100.0;
64 }
65}
類別在記憶體中的配置
1public class ClassMemoryAllocationDemo {
2
3 // 類別變數(靜態變數)- 存儲在方法區
4 private static int classVariable = 100;
5 private static final String CONSTANT = "常數值";
6
7 // 實例變數 - 存儲在堆積記憶體中
8 private int instanceVariable;
9 private String instanceString;
10 private List<Integer> instanceList;
11
12 public ClassMemoryAllocationDemo(int value, String text) {
13 // 建構子中的賦值操作
14 this.instanceVariable = value; // 基本型別值存儲在物件中
15 this.instanceString = text; // 引用存儲在物件中,實際字串在字串常數池
16 this.instanceList = new ArrayList<>(); // 引用存儲在物件中,ArrayList 實例在堆積
17 }
18
19 public void demonstrateMemoryLayout() {
20 // 局部變數 - 存儲在目前執行緒的堆疊記憶體中
21 int localInt = 200;
22 String localString = "局部字串";
23 Object localObject = new Object();
24
25 // 陣列記憶體配置示例
26 demonstrateArrayMemory();
27
28 // 物件引用關係示例
29 demonstrateObjectReferences();
30 }
31
32 private void demonstrateArrayMemory() {
33 // 一維陣列記憶體配置
34 int[] intArray = new int[5]; // 陣列物件在堆積,連續記憶體配置
35 String[] stringArray = new String[3]; // 陣列物件在堆積,元素引用在陣列中
36
37 // 陣列初始化
38 intArray[0] = 10; // 直接在陣列記憶體位置存儲值
39 intArray[1] = 20;
40 intArray[2] = 30;
41
42 stringArray[0] = "字串1"; // 存儲字串引用,實際字串在字串常數池
43 stringArray[1] = "字串2";
44 stringArray[2] = "字串3";
45
46 // 多維陣列記憶體配置
47 int[][] multiArray = new int[3][4]; // 外層陣列存儲內層陣列的引用
48
49 System.out.println("一維陣列記憶體位置:" + System.identityHashCode(intArray));
50 System.out.println("字串陣列記憶體位置:" + System.identityHashCode(stringArray));
51 System.out.println("多維陣列記憶體位置:" + System.identityHashCode(multiArray));
52 }
53
54 private void demonstrateObjectReferences() {
55 // 物件引用關係和記憶體配置
56 Person person = new Person("張三", 25);
57 Address address = new Address("台北市", "信義區");
58
59 person.setAddress(address); // person 物件持有 address 物件的引用
60
61 // 循環引用示例(需要小心記憶體洩漏)
62 Node node1 = new Node("節點1");
63 Node node2 = new Node("節點2");
64 node1.setNext(node2);
65 node2.setPrevious(node1); // 雙向引用
66
67 System.out.println("物件記憶體配置完成");
68 }
69
70 // 內部類別示例
71 public class Person {
72 private String name;
73 private int age;
74 private Address address;
75
76 public Person(String name, int age) {
77 this.name = name;
78 this.age = age;
79 }
80
81 // getter, setter 方法...
82 public void setAddress(Address address) { this.address = address; }
83 }
84
85 public class Address {
86 private String city;
87 private String district;
88
89 public Address(String city, String district) {
90 this.city = city;
91 this.district = district;
92 }
93 }
94
95 public class Node {
96 private String data;
97 private Node next;
98 private Node previous;
99
100 public Node(String data) { this.data = data; }
101 public void setNext(Node next) { this.next = next; }
102 public void setPrevious(Node previous) { this.previous = previous; }
103 }
104}
JVM 記憶體調優策略
記憶體參數配置
1@Component
2public class JVMTuningGuide {
3
4 /**
5 * JVM 記憶體參數配置建議
6 *
7 * 堆積記憶體配置:
8 * -Xms2g # 初始堆積大小
9 * -Xmx4g # 最大堆積大小
10 * -Xmn1g # 年輕世代大小
11 * -XX:SurvivorRatio=8 # Eden:Survivor = 8:1
12 *
13 * 堆疊記憶體配置:
14 * -Xss1m # 每個執行緒堆疊大小
15 *
16 * 方法區配置(JDK 8+):
17 * -XX:MetaspaceSize=256m # 初始 Metaspace 大小
18 * -XX:MaxMetaspaceSize=512m # 最大 Metaspace 大小
19 *
20 * GC 配置:
21 * -XX:+UseG1GC # 使用 G1 垃圾回收器
22 * -XX:MaxGCPauseMillis=200 # 最大 GC 暫停時間
23 */
24
25 public MemoryOptimizationReport generateOptimizationReport() {
26
27 Runtime runtime = Runtime.getRuntime();
28 long totalMemory = runtime.totalMemory();
29 long freeMemory = runtime.freeMemory();
30 long usedMemory = totalMemory - freeMemory;
31 long maxMemory = runtime.maxMemory();
32
33 // 記憶體使用率分析
34 double usedPercentage = (double) usedMemory / maxMemory * 100;
35 double allocatedPercentage = (double) totalMemory / maxMemory * 100;
36
37 List<String> recommendations = new ArrayList<>();
38
39 // 基於使用率提供建議
40 if (usedPercentage > 85) {
41 recommendations.add("記憶體使用率過高,建議增加 -Xmx 參數");
42 recommendations.add("檢查是否存在記憶體洩漏");
43 }
44
45 if (allocatedPercentage < 50) {
46 recommendations.add("已配置記憶體比例較低,考慮調整 -Xms 參數");
47 }
48
49 // GC 頻率分析
50 analyzeGCFrequency(recommendations);
51
52 return MemoryOptimizationReport.builder()
53 .totalMemory(totalMemory)
54 .usedMemory(usedMemory)
55 .freeMemory(freeMemory)
56 .maxMemory(maxMemory)
57 .usedPercentage(usedPercentage)
58 .recommendations(recommendations)
59 .timestamp(LocalDateTime.now())
60 .build();
61 }
62
63 private void analyzeGCFrequency(List<String> recommendations) {
64 List<GarbageCollectorMXBean> gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
65
66 for (GarbageCollectorMXBean gcBean : gcBeans) {
67 long collectionCount = gcBean.getCollectionCount();
68 long collectionTime = gcBean.getCollectionTime();
69
70 if (collectionCount > 0) {
71 double avgGCTime = (double) collectionTime / collectionCount;
72
73 if (avgGCTime > 100) { // 平均 GC 時間超過 100ms
74 recommendations.add(String.format(
75 "%s GC 平均時間過長(%.2fms),建議最佳化物件生命週期",
76 gcBean.getName(), avgGCTime));
77 }
78 }
79 }
80 }
81}
記憶體洩漏檢測與預防
1@Component
2public class MemoryLeakDetector {
3
4 private final WeakHashMap<Object, String> objectTracker = new WeakHashMap<>();
5 private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
6
7 @PostConstruct
8 public void startMonitoring() {
9 // 每 30 秒檢查一次記憶體使用情況
10 scheduler.scheduleAtFixedRate(this::checkMemoryLeak, 30, 30, TimeUnit.SECONDS);
11 }
12
13 public void trackObject(Object obj, String description) {
14 objectTracker.put(obj, description);
15 }
16
17 private void checkMemoryLeak() {
18 try {
19 // 強制進行 GC
20 System.gc();
21 Thread.sleep(100);
22
23 // 檢查記憶體使用趨勢
24 Runtime runtime = Runtime.getRuntime();
25 long usedMemory = runtime.totalMemory() - runtime.freeMemory();
26
27 log.info("目前記憶體使用量:{} MB,追蹤物件數量:{}",
28 usedMemory / 1024 / 1024, objectTracker.size());
29
30 // 檢查可能的記憶體洩漏模式
31 detectLeakPatterns();
32
33 } catch (InterruptedException e) {
34 Thread.currentThread().interrupt();
35 log.warn("記憶體洩漏檢測被中斷");
36 }
37 }
38
39 private void detectLeakPatterns() {
40 // 檢查常見的記憶體洩漏模式
41
42 // 1. 檢查執行緒本地變數
43 checkThreadLocalVariables();
44
45 // 2. 檢查事件監聽器
46 checkEventListeners();
47
48 // 3. 檢查快取大小
49 checkCacheSize();
50 }
51
52 private void checkThreadLocalVariables() {
53 Thread[] threads = new Thread[Thread.activeCount()];
54 Thread.enumerate(threads);
55
56 for (Thread thread : threads) {
57 if (thread != null) {
58 // 注意:這裡只是示例,實際檢查 ThreadLocal 需要更複雜的邏輯
59 String threadInfo = String.format("執行緒:%s,狀態:%s",
60 thread.getName(), thread.getState());
61 log.debug(threadInfo);
62 }
63 }
64 }
65
66 private void checkEventListeners() {
67 // 檢查 Spring 事件監聽器數量
68 // 這裡可以實作具體的監聽器計數邏輯
69 log.debug("檢查事件監聽器數量");
70 }
71
72 private void checkCacheSize() {
73 // 檢查各種快取的大小
74 // 這裡可以實作具體的快取監控邏輯
75 log.debug("檢查快取大小");
76 }
77
78 @PreDestroy
79 public void cleanup() {
80 scheduler.shutdown();
81 try {
82 if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
83 scheduler.shutdownNow();
84 }
85 } catch (InterruptedException e) {
86 scheduler.shutdownNow();
87 Thread.currentThread().interrupt();
88 }
89 }
90}
實際應用案例
高效能記憶體管理範例
1@Service
2public class HighPerformanceMemoryManager {
3
4 // 物件池,減少物件建立和銷毀的開銷
5 private final ObjectPool<StringBuilder> stringBuilderPool;
6 private final ObjectPool<ByteBuffer> byteBufferPool;
7
8 public HighPerformanceMemoryManager() {
9 // 初始化物件池
10 this.stringBuilderPool = new GenericObjectPool<>(new StringBuilderPooledObjectFactory());
11 this.byteBufferPool = new GenericObjectPool<>(new ByteBufferPooledObjectFactory());
12 }
13
14 public String processLargeText(List<String> textList) {
15 StringBuilder sb = null;
16 try {
17 // 從物件池獲取 StringBuilder
18 sb = stringBuilderPool.borrowObject();
19 sb.setLength(0); // 重置內容
20
21 // 處理大量文字資料
22 for (String text : textList) {
23 sb.append(text).append("\n");
24 }
25
26 return sb.toString();
27
28 } catch (Exception e) {
29 log.error("處理文字時發生錯誤", e);
30 return "";
31 } finally {
32 // 歸還物件到池中
33 if (sb != null) {
34 try {
35 stringBuilderPool.returnObject(sb);
36 } catch (Exception e) {
37 log.warn("歸還 StringBuilder 到物件池失敗", e);
38 }
39 }
40 }
41 }
42
43 public byte[] processLargeData(List<byte[]> dataList) {
44 ByteBuffer buffer = null;
45 try {
46 // 從物件池獲取 ByteBuffer
47 buffer = byteBufferPool.borrowObject();
48 buffer.clear(); // 重置狀態
49
50 // 處理大量位元組資料
51 for (byte[] data : dataList) {
52 if (buffer.remaining() >= data.length) {
53 buffer.put(data);
54 } else {
55 // 如果緩衝區不夠大,需要擴展或處理
56 log.warn("緩衝區容量不足,資料長度:{}, 剩餘容量:{}",
57 data.length, buffer.remaining());
58 break;
59 }
60 }
61
62 buffer.flip();
63 byte[] result = new byte[buffer.remaining()];
64 buffer.get(result);
65
66 return result;
67
68 } catch (Exception e) {
69 log.error("處理位元組資料時發生錯誤", e);
70 return new byte[0];
71 } finally {
72 // 歸還物件到池中
73 if (buffer != null) {
74 try {
75 byteBufferPool.returnObject(buffer);
76 } catch (Exception e) {
77 log.warn("歸還 ByteBuffer 到物件池失敗", e);
78 }
79 }
80 }
81 }
82
83 // StringBuilder 物件池工廠
84 private static class StringBuilderPooledObjectFactory
85 extends BasePooledObjectFactory<StringBuilder> {
86
87 @Override
88 public StringBuilder create() {
89 return new StringBuilder(1024); // 預設容量 1024 個字元
90 }
91
92 @Override
93 public PooledObject<StringBuilder> wrap(StringBuilder obj) {
94 return new DefaultPooledObject<>(obj);
95 }
96
97 @Override
98 public void passivateObject(PooledObject<StringBuilder> p) {
99 // 歸還前重置物件狀態
100 p.getObject().setLength(0);
101 }
102 }
103
104 // ByteBuffer 物件池工廠
105 private static class ByteBufferPooledObjectFactory
106 extends BasePooledObjectFactory<ByteBuffer> {
107
108 @Override
109 public ByteBuffer create() {
110 return ByteBuffer.allocate(8192); // 預設容量 8KB
111 }
112
113 @Override
114 public PooledObject<ByteBuffer> wrap(ByteBuffer obj) {
115 return new DefaultPooledObject<>(obj);
116 }
117
118 @Override
119 public void passivateObject(PooledObject<ByteBuffer> p) {
120 // 歸還前重置物件狀態
121 p.getObject().clear();
122 }
123 }
124}
效能監控與分析
JVM 記憶體監控儀表板
1@RestController
2@RequestMapping("/api/memory")
3public class MemoryMonitoringController {
4
5 private final MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
6 private final RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean();
7
8 @GetMapping("/status")
9 public ResponseEntity<MemoryStatusResponse> getMemoryStatus() {
10
11 // 堆積記憶體使用情況
12 MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
13
14 // 非堆積記憶體使用情況(方法區等)
15 MemoryUsage nonHeapUsage = memoryBean.getNonHeapMemoryUsage();
16
17 // 詳細記憶體池資訊
18 List<MemoryPoolInfo> memoryPools = ManagementFactory.getMemoryPoolMXBeans()
19 .stream()
20 .map(pool -> MemoryPoolInfo.builder()
21 .name(pool.getName())
22 .type(pool.getType().toString())
23 .usage(pool.getUsage())
24 .peakUsage(pool.getPeakUsage())
25 .collectionUsage(pool.getCollectionUsage())
26 .build())
27 .collect(Collectors.toList());
28
29 // GC 資訊
30 List<GCInfo> gcInfos = ManagementFactory.getGarbageCollectorMXBeans()
31 .stream()
32 .map(gc -> GCInfo.builder()
33 .name(gc.getName())
34 .collectionCount(gc.getCollectionCount())
35 .collectionTime(gc.getCollectionTime())
36 .memoryPoolNames(Arrays.asList(gc.getMemoryPoolNames()))
37 .build())
38 .collect(Collectors.toList());
39
40 MemoryStatusResponse response = MemoryStatusResponse.builder()
41 .heapUsage(convertMemoryUsage(heapUsage))
42 .nonHeapUsage(convertMemoryUsage(nonHeapUsage))
43 .memoryPools(memoryPools)
44 .gcInfos(gcInfos)
45 .uptime(runtimeBean.getUptime())
46 .vmName(runtimeBean.getVmName())
47 .vmVersion(runtimeBean.getVmVersion())
48 .timestamp(LocalDateTime.now())
49 .build();
50
51 return ResponseEntity.ok(response);
52 }
53
54 @GetMapping("/analysis")
55 public ResponseEntity<MemoryAnalysisResponse> analyzeMemoryUsage() {
56
57 MemoryAnalysisResponse analysis = performMemoryAnalysis();
58 return ResponseEntity.ok(analysis);
59 }
60
61 @PostMapping("/gc")
62 public ResponseEntity<String> triggerGC() {
63 // 注意:在生產環境中慎用手動觸發 GC
64 long beforeGC = getUsedMemory();
65
66 System.gc();
67
68 // 等待 GC 完成
69 try {
70 Thread.sleep(100);
71 } catch (InterruptedException e) {
72 Thread.currentThread().interrupt();
73 }
74
75 long afterGC = getUsedMemory();
76 long freedMemory = beforeGC - afterGC;
77
78 String result = String.format("GC 執行完成,釋放記憶體:%d KB",
79 freedMemory / 1024);
80
81 return ResponseEntity.ok(result);
82 }
83
84 private MemoryAnalysisResponse performMemoryAnalysis() {
85 // 實作記憶體分析邏輯
86 List<String> warnings = new ArrayList<>();
87 List<String> recommendations = new ArrayList<>();
88
89 // 分析堆積記憶體使用率
90 MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
91 double heapUsagePercentage = (double) heapUsage.getUsed() / heapUsage.getMax() * 100;
92
93 if (heapUsagePercentage > 85) {
94 warnings.add("堆積記憶體使用率過高:" + String.format("%.2f%%", heapUsagePercentage));
95 recommendations.add("考慮增加 JVM 堆積記憶體大小");
96 recommendations.add("檢查應用程式是否存在記憶體洩漏");
97 }
98
99 // 分析 GC 頻率
100 analyzeGCFrequency(warnings, recommendations);
101
102 return MemoryAnalysisResponse.builder()
103 .heapUsagePercentage(heapUsagePercentage)
104 .warnings(warnings)
105 .recommendations(recommendations)
106 .analysisTime(LocalDateTime.now())
107 .build();
108 }
109
110 private void analyzeGCFrequency(List<String> warnings, List<String> recommendations) {
111 // GC 頻率分析邏輯
112 ManagementFactory.getGarbageCollectorMXBeans().forEach(gc -> {
113 long collectionCount = gc.getCollectionCount();
114 long collectionTime = gc.getCollectionTime();
115 long uptime = runtimeBean.getUptime();
116
117 if (collectionCount > 0 && uptime > 0) {
118 double avgGCInterval = (double) uptime / collectionCount;
119 double avgGCTime = (double) collectionTime / collectionCount;
120
121 if (avgGCInterval < 10000) { // 平均 GC 間隔小於 10 秒
122 warnings.add(String.format("%s GC 頻率過高,平均間隔:%.2f 秒",
123 gc.getName(), avgGCInterval / 1000.0));
124 }
125
126 if (avgGCTime > 100) { // 平均 GC 時間超過 100ms
127 warnings.add(String.format("%s GC 時間過長,平均時間:%.2f ms",
128 gc.getName(), avgGCTime));
129 }
130 }
131 });
132 }
133
134 private long getUsedMemory() {
135 return memoryBean.getHeapMemoryUsage().getUsed();
136 }
137
138 private MemoryUsageInfo convertMemoryUsage(MemoryUsage usage) {
139 return MemoryUsageInfo.builder()
140 .init(usage.getInit())
141 .used(usage.getUsed())
142 .committed(usage.getCommitted())
143 .max(usage.getMax())
144 .usagePercentage(usage.getMax() > 0 ?
145 (double) usage.getUsed() / usage.getMax() * 100 : 0)
146 .build();
147 }
148}
總結
JVM 記憶體管理是 Java 應用程式效能的核心基礎。透過深入理解堆疊記憶體與堆積記憶體的運作機制,我們能夠:
關鍵要點
- 堆疊記憶體:執行緒私有,用於方法調用和局部變數,採用 LIFO 結構
- 堆積記憶體:執行緒共享,用於物件實例和陣列,分為年輕世代和老年世代
- 垃圾回收:自動記憶體管理機制,需要根據應用特性選擇合適的 GC 策略
- 記憶體調優:透過 JVM 參數調整和程式碼最佳化提升效能
最佳實踐建議
- 合理設定 JVM 記憶體參數
- 避免記憶體洩漏和過度物件建立
- 使用物件池管理昂貴物件
- 定期監控記憶體使用情況
- 根據 GC 日誌調整參數
掌握這些記憶體管理技巧,有助於開發高效能、穩定的 Java 應用程式。