JVM 記憶體深度解析:堆疊與堆積記憶體完整指南

前言

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 應用程式。