06-Java语言核心-JVM原理-JVM内存区域详解

2 阅读21分钟

JVM内存区域详解

一、知识概述

Java虚拟机(JVM)在执行Java程序时,会将其管理的内存划分为若干个不同的数据区域。这些区域有各自的用途、创建时间和销毁时间。理解JVM内存区域是掌握Java性能优化、排查内存问题的基础。

JVM内存区域划分

根据《Java虚拟机规范》,JVM管理的内存分为以下几个区域:

┌─────────────────────────────────────────────────────────────┐
│                        JVM 运行时数据区                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │
│  │   程序计数器  │  │   Java虚拟机栈 │  │   本地方法栈  │         │
│  │   (线程私有)  │  │   (线程私有)   │  │   (线程私有)  │         │
│  └─────────────┘  └─────────────┘  └─────────────┘         │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                    Java堆 (Heap)                      │   │
│  │                   (线程共享,GC主要区域)                │   │
│  │  ┌─────────────┐  ┌─────────────┐                    │   │
│  │  │   新生代     │  │    老年代    │                    │   │
│  │  │ Eden/S0/S1  │  │             │                    │   │
│  │  └─────────────┘  └─────────────┘                    │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                   方法区 (Method Area)                │   │
│  │                   (线程共享,存储类信息)                │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                   运行时常量池                         │   │
│  │               (方法区的一部分)                         │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                   直接内存 (Direct Memory)            │   │
│  │               (NIO使用,非JVM规范定义)                 │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

线程私有 vs 线程共享

区域类型生命周期存储内容
程序计数器线程私有线程生命周期当前执行的字节码行号
Java虚拟机栈线程私有线程生命周期栈帧(局部变量表、操作数栈等)
本地方法栈线程私有线程生命周期Native方法调用信息
Java堆线程共享JVM生命周期对象实例、数组
方法区线程共享JVM生命周期类信息、常量、静态变量

二、知识点详细讲解

2.1 程序计数器(Program Counter Register)

程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。

特点
  • 线程私有:每个线程都有独立的程序计数器
  • 唯一不会OOM的区域:如果执行的是Java方法,计数器记录的是字节码指令地址;如果执行的是Native方法,计数器值为空
  • 生命周期:随线程创建而创建,随线程结束而销毁
工作原理
/**
 * 程序计数器工作原理演示
 */
public class ProgramCounterDemo {
    
    public static void main(String[] args) {
        int a = 1;          // PC: 0
        int b = 2;          // PC: 1
        int c = add(a, b);  // PC: 2 -> 跳转到add方法
        System.out.println(c); // PC: 3
    }
    
    public static int add(int x, int y) {
        return x + y;       // PC: add方法中的指令地址
    }
}

/*
字节码视角:
  public static void main(java.lang.String[]);
    Code:
       0: iconst_1          // PC = 0
       1: istore_1          // PC = 1
       2: iconst_2          // PC = 2
       3: istore_2          // PC = 3
       4: iload_1           // PC = 4
       5: iload_2           // PC = 5
       6: invokestatic #2   // PC = 6, 调用add方法
       9: istore_3          // PC = 9, 从方法返回后继续
      10: getstatic #3      // PC = 10
      ...
*/
多线程执行
线程A                           线程B
┌─────────────┐                ┌─────────────┐
│ PC: 0x1000  │                │ PC: 0x2000  │
│ 执行方法A    │                │ 执行方法B    │
└─────────────┘                └─────────────┘
      ↓                              ↓
   时间片切换                     时间片切换
      ↓                              ↓
┌─────────────┐                ┌─────────────┐
│ PC: 0x1004  │                │ PC: 0x2008  │
│ 恢复执行     │                │ 恢复执行     │
└─────────────┘                └─────────────┘

每个线程有独立的PC,切换后能恢复到正确的执行位置

2.2 Java虚拟机栈(Java Virtual Machine Stack)

Java虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息。

栈帧结构
┌─────────────────────────────────────┐
│             栈帧 (Stack Frame)       │
├─────────────────────────────────────┤
│                                     │
│  ┌─────────────────────────────┐   │
│  │      局部变量表 (Local Variables)    │
│  │  [0] this                   │   │
│  │  [1] arg1                   │   │
│  │  [2] arg2                   │   │
│  │  [3] local_var1             │   │
│  │  [4] local_var2             │   │
│  └─────────────────────────────┘   │
│                                     │
│  ┌─────────────────────────────┐   │
│  │      操作数栈 (Operand Stack)       │
│  │        ┌───┐                     │   │
│  │        │ 5 │  <- 栈顶             │   │
│  │        ├───┤                     │   │
│  │        │ 3 │                      │   │
│  │        └───┘                     │   │
│  └─────────────────────────────┘   │
│                                     │
│  ┌─────────────────────────────┐   │
│  │      动态连接 (Dynamic Linking)     │
│  │  -> 指向运行时常量池的方法引用      │   │
│  └─────────────────────────────┘   │
│                                     │
│  ┌─────────────────────────────┐   │
│  │      方法返回地址 (Return Address)  │
│  │  -> 方法正常退出或异常退出的地址    │   │
│  └─────────────────────────────┘   │
│                                     │
│  ┌─────────────────────────────┐   │
│  │      附加信息 (附加信息)            │
│  └─────────────────────────────┘   │
│                                     │
└─────────────────────────────────────┘
局部变量表
/**
 * 局部变量表示例
 */
public class LocalVariableTableDemo {
    
    // 局部变量表分析
    public static int calculate(int a, int b) {
        // 局部变量表:
        // Slot[0] = a (参数)
        // Slot[1] = b (参数)
        
        int c = a + b;     // Slot[2] = c (局部变量)
        int d = c * 2;     // Slot[3] = d (局部变量)
        
        {
            int e = d + 1; // Slot[4] = e (作用域内有效)
            // e 在此作用域结束后,Slot[4] 可被复用
        }
        
        int f = c - 1;     // Slot[4] = f (复用之前的Slot)
        
        return f;
    }
    
    // 实例方法的局部变量表
    public void instanceMethod(String name, int age) {
        // 局部变量表:
        // Slot[0] = this (实例方法隐含参数)
        // Slot[1] = name
        // Slot[2] = age
        
        System.out.println(name + ": " + age);
    }
    
    // 长整型和双精度浮点占用两个Slot
    public void doubleSlot(long id, double value) {
        // 局部变量表:
        // Slot[0] = this
        // Slot[1-2] = id (long占用两个Slot)
        // Slot[3-4] = value (double占用两个Slot)
    }
}
操作数栈
/**
 * 操作数栈工作原理
 * 计算: (1 + 2) * 3
 */
public class OperandStackDemo {
    
    public static int calculate() {
        /*
        字节码执行过程:
        
        0: iconst_1      // 将常量1压入操作数栈
           操作数栈: [1]
        
        1: iconst_2      // 将常量2压入操作数栈
           操作数栈: [1, 2]
        
        2: iadd          // 弹出两个值相加,结果压栈
           操作数栈: [3]
        
        3: iconst_3      // 将常量3压入操作数栈
           操作数栈: [3, 3]
        
        4: imul          // 弹出两个值相乘,结果压栈
           操作数栈: [9]
        
        5: ireturn       // 返回栈顶值
        */
        return (1 + 2) * 3;
    }
    
    public static void main(String[] args) {
        int result = calculate();
        System.out.println(result);  // 输出: 9
    }
}
栈溢出错误
/**
 * 栈溢出演示
 */
public class StackOverflowDemo {
    
    private static int count = 0;
    
    /**
     * 无限递归导致StackOverflowError
     */
    public static void recursiveCall() {
        count++;
        recursiveCall();  // 无限递归
    }
    
    /**
     * 通过减少局部变量延迟溢出
     */
    public static void smallFrame() {
        count++;
        smallFrame();  // 局部变量少,栈帧小
    }
    
    /**
     * 大量局部变量加速溢出
     */
    public static void largeFrame() {
        // 大量局部变量增加栈帧大小
        long a1, a2, a3, a4, a5, a6, a7, a8, a9, a10;
        long b1, b2, b3, b4, b5, b6, b7, b8, b9, b10;
        long c1, c2, c3, c4, c5, c6, c7, c8, c9, c10;
        long d1, d2, d3, d4, d5, d6, d7, d8, d9, d10;
        
        count++;
        largeFrame();
    }
    
    public static void main(String[] args) {
        try {
            recursiveCall();
        } catch (StackOverflowError e) {
            System.out.println("StackOverflowError at depth: " + count);
            System.out.println("栈深度取决于:");
            System.out.println("  1. -Xss 参数设置(默认1MB)");
            System.out.println("  2. 每个栈帧的大小");
            System.out.println("  3. 栈帧中局部变量的数量");
        }
        
        // 演示不同栈帧大小的影响
        count = 0;
        try {
            largeFrame();
        } catch (StackOverflowError e) {
            System.out.println("\nLargeFrame overflow at: " + count);
        }
    }
}

/*
运行示例:
  java -Xss256k StackOverflowDemo    # 小栈,快速溢出
  java -Xss1m StackOverflowDemo      # 默认大小
  java -Xss2m StackOverflowDemo      # 大栈,深度更大
*/

2.3 本地方法栈(Native Method Stack)

本地方法栈与Java虚拟机栈类似,区别在于Java虚拟机栈为Java方法服务,而本地方法栈为Native方法服务。

/**
 * 本地方法栈示例
 */
public class NativeMethodStackDemo {
    
    public static void main(String[] args) {
        // Object.hashCode() 是native方法
        Object obj = new Object();
        int hash = obj.hashCode();  // 调用本地方法
        
        // System.currentTimeMillis() 是native方法
        long time = System.currentTimeMillis();  // 调用本地方法
        
        // Thread.start() 最终调用native方法启动线程
        Thread thread = new Thread(() -> {
            System.out.println("Thread running");
        });
        thread.start();  // 内部调用native start0()
        
        // Class.forName() 内部使用native方法
        try {
            Class<?> clazz = Class.forName("java.lang.String");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        
        // Unsafe类大量使用native方法
        // sun.misc.Unsafe 提供直接内存访问能力
    }
}

/*
本地方法示例:

// Object类
public native int hashCode();
protected native Object clone() throws CloneNotSupportedException;

// System类
public static native long currentTimeMillis();
public static native void arraycopy(Object src, int srcPos,
                                    Object dest, int destPos, int length);

// Thread类
private native void start0();
private native boolean isInterrupted(boolean clearInterrupted);

// Class类
private native String getName0();
*/

2.4 Java堆(Java Heap)

Java堆是Java虚拟机管理的内存中最大的一块,被所有线程共享,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

堆内存结构
┌───────────────────────────────────────────────────────────────┐
│                        Java Heap                               │
├───────────────────────────────────────────────────────────────┤
│                                                                │
│  ┌─────────────────────────────────┐  ┌─────────────────────┐ │
│  │          新生代 (Young Gen)      │  │   老年代 (Old Gen)   │ │
│  │         (约占堆的1/3)            │  │   (约占堆的2/3)      │ │
│  │  ┌───────────┬──────┬──────┐    │  │                     │ │
│  │  │   Eden    │ S0   │ S1   │    │  │                     │ │
│  │  │  (8/10)   │(1/10)│(1/10)│    │  │                     │ │
│  │  └───────────┴──────┴──────┘    │  │                     │ │
│  │                                 │  │                     │ │
│  │  对象先在Eden分配               │  │   长期存活的对象     │ │
│  │  GC后存活对象进入Survivor       │  │   从Survivor晋升    │ │
│  │  达到年龄阈值后晋升老年代       │  │                     │ │
│  └─────────────────────────────────┘  └─────────────────────┘ │
│                                                                │
│  新生代GC (Minor GC)                老年代GC (Major/Full GC)   │
│  频繁、速度快                       较少、速度慢                │
│                                                                │
└───────────────────────────────────────────────────────────────┘

相关参数:
  -Xms: 堆初始大小
  -Xmx: 堆最大大小
  -Xmn: 新生代大小
  -XX:NewRatio: 新生代与老年代比例
  -XX:SurvivorRatio: Eden与Survivor比例
  -XX:MaxTenuringThreshold: 晋升老年代年龄阈值
对象分配过程
/**
 * 对象在堆中的分配过程
 */
public class HeapAllocationDemo {
    
    public static void main(String[] args) {
        /*
        对象分配流程:
        
        1. 新对象先尝试在Eden区分配
           ┌─────────────────────────────────┐
           │  Eden: [obj1][obj2][obj3][... ] │
           └─────────────────────────────────┘
        
        2. Eden区满了,触发Minor GC
           存活对象被复制到Survivor区(假设S0)
           ┌─────────────────────────────────┐
           │  Eden: [空]                     │
           └─────────────────────────────────┘
           ┌──────┐
           │ S0:  │ [存活obj1][存活obj2]
           └──────┘
        
        3. 下次Minor GC,Eden和S0中存活对象复制到S1
           ┌─────────────────────────────────┐
           │  Eden: [空]                     │
           └─────────────────────────────────┘
           ┌──────┐        ┌──────┐
           │ S0:  │ [空]   │ S1:  │ [存活对象]
           └──────┘        └──────┘
        
        4. Survivor中对象年龄达到阈值,晋升老年代
           ┌──────┐
           │ Old: │ [年龄足够的老对象]
           └──────┘
        
        5. 大对象直接进入老年代
           -XX:PretenureSizeThreshold 设置阈值
        */
        
        // 普通对象在Eden分配
        for (int i = 0; i < 100; i++) {
            byte[] data = new byte[1024];  // 小对象,Eden分配
        }
        
        // 大对象可能直接进入老年代
        byte[] largeData = new byte[10 * 1024 * 1024];  // 10MB
        
        // 长期存活对象最终进入老年代
        List<byte[]> longLived = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            longLived.add(new byte[1024]);
        }
    }
}
堆内存溢出
import java.util.*;

/**
 * 堆内存溢出演示
 * java -Xms20m -Xmx20m HeapOOMDemo
 */
public class HeapOOMDemo {
    
    static class OOMObject {
        private byte[] data = new byte[1024 * 1024];  // 1MB
    }
    
    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<>();
        
        System.out.println("开始创建对象...");
        System.out.println("堆大小: 20MB");
        System.out.println("每个对象: 1MB + 对象头");
        System.out.println();
        
        try {
            int count = 0;
            while (true) {
                list.add(new OOMObject());
                count++;
                if (count % 5 == 0) {
                    System.out.println("已创建 " + count + " 个对象");
                }
            }
        } catch (OutOfMemoryError e) {
            System.out.println("\nOutOfMemoryError: Java heap space");
            System.out.println("总共创建了约 " + list.size() + " 个对象");
            System.out.println("\n解决方法:");
            System.out.println("  1. 增大堆内存: -Xmx");
            System.out.println("  2. 检查是否存在内存泄漏");
            System.out.println("  3. 使用内存分析工具: jvisualvm, MAT");
        }
    }
}

/*
运行结果:
  开始创建对象...
  堆大小: 20MB
  每个对象: 1MB + 对象头
  
  已创建 5 个对象
  已创建 10 个对象
  已创建 15 个对象
  
  OutOfMemoryError: Java heap space
  总共创建了约 18 个对象
*/
堆内存分析
import java.lang.management.*;
import java.util.*;

/**
 * 堆内存使用情况监控
 */
public class HeapMonitorDemo {
    
    public static void main(String[] args) throws InterruptedException {
        // 获取内存管理Bean
        MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
        MemoryUsage heapUsage = memoryMXBean.getHeapMemoryUsage();
        
        System.out.println("=== 堆内存配置 ===");
        System.out.println("初始大小: " + toMB(heapUsage.getInit()) + " MB");
        System.out.println("当前使用: " + toMB(heapUsage.getUsed()) + " MB");
        System.out.println("已提交: " + toMB(heapUsage.getCommitted()) + " MB");
        System.out.println("最大可用: " + toMB(heapUsage.getMax()) + " MB");
        
        // 获取各个内存池信息
        System.out.println("\n=== 内存池详情 ===");
        List<MemoryPoolMXBean> memoryPools = ManagementFactory.getMemoryPoolMXBeans();
        
        for (MemoryPoolMXBean pool : memoryPools) {
            if (pool.getType() == MemoryType.HEAP) {
                MemoryUsage usage = pool.getUsage();
                System.out.println("\n" + pool.getName() + ":");
                System.out.println("  使用: " + toMB(usage.getUsed()) + " MB");
                System.out.println("  已提交: " + toMB(usage.getCommitted()) + " MB");
                System.out.println("  最大: " + toMB(usage.getMax()) + " MB");
            }
        }
        
        // 模拟内存使用
        System.out.println("\n=== 创建对象后 ===");
        List<byte[]> data = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            data.add(new byte[1024 * 1024]);  // 1MB
        }
        
        heapUsage = memoryMXBean.getHeapMemoryUsage();
        System.out.println("当前使用: " + toMB(heapUsage.getUsed()) + " MB");
        
        // GC前后的内存变化
        System.out.println("\n=== 执行GC ===");
        long beforeGC = memoryMXBean.getHeapMemoryUsage().getUsed();
        System.out.println("GC前: " + toMB(beforeGC) + " MB");
        
        System.gc();
        Thread.sleep(100);
        
        long afterGC = memoryMXBean.getHeapMemoryUsage().getUsed();
        System.out.println("GC后: " + toMB(afterGC) + " MB");
        System.out.println("回收: " + toMB(beforeGC - afterGC) + " MB");
    }
    
    private static String toMB(long bytes) {
        return String.format("%.2f", bytes / 1024.0 / 1024.0);
    }
}

2.5 方法区(Method Area)

方法区与Java堆一样,是各个线程共享的内存区域,用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。

方法区存储内容
┌─────────────────────────────────────────────────────────────┐
│                       方法区 (Method Area)                   │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                    类型信息                           │   │
│  │  - 类名、访问修饰符                                   │   │
│  │  - 父类、实现的接口                                   │   │
│  │  - 字段信息(名称、类型、修饰符)                     │   │
│  │  - 方法信息(名称、返回类型、参数、字节码)           │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                    运行时常量池                       │   │
│  │  - 字面量(字符串、final常量)                       │   │
│  │  - 符号引用(类和接口的全限定名、字段和方法描述)     │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                    静态变量                           │   │
│  │  - static 修饰的类变量                               │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                    JIT编译代码                        │   │
│  │  - 即时编译器生成的本地代码                          │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

JDK版本差异:
  JDK 7及之前: 永久代 (Permanent Generation)
    - -XX:PermSize, -XX:MaxPermSize
    - 固定大小,容易OOM
  
  JDK 8及之后: 元空间 (Metaspace)
    - -XX:MetaspaceSize, -XX:MaxMetaspaceSize
    - 使用本地内存,默认无上限
运行时常量池
/**
 * 运行时常量池示例
 */
public class RuntimeConstantPoolDemo {
    
    public static void main(String[] args) {
        // === 字符串常量池 ===
        
        // 字面量创建,放入字符串常量池
        String s1 = "Hello";
        String s2 = "Hello";
        System.out.println("s1 == s2: " + (s1 == s2));  // true
        
        // new创建,堆中创建新对象
        String s3 = new String("Hello");
        System.out.println("s1 == s3: " + (s1 == s3));  // false
        System.out.println("s1.intern() == s3.intern(): " + 
            (s1.intern() == s3.intern()));  // true
        
        // === 编译期常量 ===
        System.out.println("\n编译期常量:");
        System.out.println("MAX_VALUE = " + Integer.MAX_VALUE);
        System.out.println("PI = " + Math.PI);
        
        // === 符号引用 -> 直接引用 ===
        System.out.println("\n符号引用解析:");
        
        // 解析前:java.lang.Object 是符号引用
        // 解析后:指向方法区中Object类的直接指针
        Object obj = new Object();
        
        // 方法调用的符号引用
        // 编译时:只知道方法名和描述符
        // 运行时:解析为方法的直接引用(入口地址)
        String str = obj.toString();
        System.out.println(str);
        
        // === 常量池溢出 (JDK 7之前) ===
        // JDK 7之后,字符串常量池移到堆中
        System.out.println("\n常量池容量测试:");
        testStringPool();
    }
    
    /**
     * 测试字符串常量池
     * JDK 7+: 池在堆中,受堆大小限制
     * JDK 6: 池在永久代,受PermGen限制
     */
    private static void testStringPool() {
        Set<String> pool = new HashSet<>();
        long start = System.currentTimeMillis();
        
        try {
            int i = 0;
            while (true) {
                // intern() 将字符串加入常量池
                String str = "String" + i;
                str.intern();
                pool.add(str);
                i++;
                
                if (i % 100000 == 0) {
                    System.out.println("已添加 " + i + " 个字符串到常量池");
                }
            }
        } catch (OutOfMemoryError e) {
            long end = System.currentTimeMillis();
            System.out.println("OOM after adding " + pool.size() + " strings");
            System.out.println("Time: " + (end - start) + "ms");
        }
    }
    
    /**
     * Class常量池内容
     */
    public static void showClassConstantPool() throws Exception {
        // 使用javap查看常量池
        // javap -v RuntimeConstantPoolDemo.class
        
        /*
        Constant pool:
           #1 = Methodref          #10.#29        // java/lang/Object."<init>":()V
           #2 = String             #30            // Hello
           #3 = Fieldref           #31.#32        // java/lang/System.out:Ljava/io/PrintStream;
           #4 = Class              #33            // java/lang/StringBuilder
           #5 = Methodref          #34.#35        // ...StringBuilder.append:(Ljava/lang/String;)...
           #6 = Methodref          #34.#36        // ...StringBuilder.toString:()Ljava/lang/String;
           ...
        */
    }
}
方法区溢出
import java.lang.reflect.*;
import java.util.*;

/**
 * 方法区溢出演示
 * JDK 8: java -XX:MaxMetaspaceSize=10m MetaspaceOOMDemo
 * JDK 7: java -XX:MaxPermSize=10m PermGenOOMDemo
 */
public class MetaspaceOOMDemo {
    
    public static void main(String[] args) {
        System.out.println("=== 方法区/元空间溢出演示 ===");
        System.out.println("限制: 10MB");
        System.out.println();
        
        // 使用CGLib动态生成大量类
        Enhancer enhancer = new Enhancer();
        
        try {
            int count = 0;
            while (true) {
                // 每次创建一个新的类
                enhancer.setSuperclass(OOMObject.class);
                enhancer.setUseCache(false);
                enhancer.setCallback(new MethodInterceptor() {
                    @Override
                    public Object intercept(Object obj, Method method, 
                            Object[] args, MethodProxy proxy) throws Throwable {
                        return proxy.invokeSuper(obj, args);
                    }
                });
                
                Object obj = enhancer.create();
                count++;
                
                if (count % 100 == 0) {
                    System.out.println("已生成 " + count + " 个类");
                }
            }
        } catch (OutOfMemoryError e) {
            System.out.println("\nOutOfMemoryError: Metaspace");
            System.out.println("\n原因:");
            System.out.println("  1. 加载太多类(CGLib、动态代理)");
            System.out.println("  2. 元空间/永久代设置过小");
            System.out.println("  3. 类加载器泄漏");
            System.out.println("\n解决方法:");
            System.out.println("  JDK 8+: 增大 -XX:MaxMetaspaceSize");
            System.out.println("  JDK 7: 增大 -XX:MaxPermSize");
            System.out.println("  使用类加载器分析工具");
        }
    }
    
    static class OOMObject {
        public void method() {}
    }
}

// 需要CGLib依赖
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

2.6 直接内存(Direct Memory)

直接内存不是虚拟机运行时数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域。但是这部分内存也被频繁地使用,而且也可能导致OutOfMemoryError异常。

import java.nio.*;
import java.nio.channels.*;
import java.io.*;
import java.util.*;

/**
 * 直接内存示例
 */
public class DirectMemoryDemo {
    
    public static void main(String[] args) throws Exception {
        
        // === 1. 直接内存 vs 堆内存 ===
        System.out.println("=== 直接内存 vs 堆内存 ===\n");
        
        // 堆内存Buffer
        ByteBuffer heapBuffer = ByteBuffer.allocate(1024 * 1024);  // 1MB堆内
        System.out.println("Heap Buffer:");
        System.out.println("  isDirect: " + heapBuffer.isDirect());
        System.out.println("  在Java堆中分配");
        System.out.println("  IO时需要复制到本地内存");
        
        // 直接内存Buffer
        ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024 * 1024);  // 1MB堆外
        System.out.println("\nDirect Buffer:");
        System.out.println("  isDirect: " + directBuffer.isDirect());
        System.out.println("  在本地内存中分配");
        System.out.println("  IO时直接使用,零拷贝");
        
        // === 2. 性能对比 ===
        System.out.println("\n=== IO性能对比 ===\n");
        
        // 准备测试文件
        File tempFile = File.createTempFile("test", ".dat");
        tempFile.deleteOnExit();
        writeTestData(tempFile, 100 * 1024 * 1024);  // 100MB
        
        // 堆内存读写
        long heapTime = testHeapIO(tempFile);
        System.out.println("Heap Buffer IO: " + heapTime + "ms");
        
        // 直接内存读写
        long directTime = testDirectIO(tempFile);
        System.out.println("Direct Buffer IO: " + directTime + "ms");
        
        System.out.println("\n直接内存快 " + 
            String.format("%.1f", (double)heapTime / directTime) + " 倍");
        
        // === 3. 直接内存监控 ===
        System.out.println("\n=== 直接内存使用 ===\n");
        
        // 查看直接内存限制
        long maxDirect = sun.misc.VM.maxDirectMemory();
        System.out.println("最大直接内存: " + toMB(maxDirect) + " MB");
        
        // 分配直接内存
        List<ByteBuffer> buffers = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            buffers.add(ByteBuffer.allocateDirect(10 * 1024 * 1024));  // 10MB each
        }
        
        // 查看已使用
        long usedDirect = getUsedDirectMemory();
        System.out.println("已使用直接内存: " + toMB(usedDirect) + " MB");
        
        // === 4. 直接内存溢出 ===
        System.out.println("\n=== 直接内存溢出测试 ===");
        System.out.println("限制: -XX:MaxDirectMemorySize=50m");
        System.out.println();
        
        try {
            // 分配超过限制的直接内存
            ByteBuffer.allocateDirect(100 * 1024 * 1024);  // 100MB
        } catch (OutOfMemoryError e) {
            System.out.println("OutOfMemoryError: Direct buffer memory");
            System.out.println("\n原因:");
            System.out.println("  直接内存超过 -XX:MaxDirectMemorySize 限制");
            System.out.println("\n解决方法:");
            System.out.println("  1. 增大 -XX:MaxDirectMemorySize");
            System.out.println("  2. 及时释放DirectBuffer");
            System.out.println("  3. 使用 Cleaner 清理");
        }
        
        // 清理临时文件
        tempFile.delete();
    }
    
    private static void writeTestData(File file, long size) throws IOException {
        try (RandomAccessFile raf = new RandomAccessFile(file, "rw")) {
            raf.setLength(size);
        }
    }
    
    private static long testHeapIO(File file) throws IOException {
        long start = System.currentTimeMillis();
        
        try (FileChannel channel = new FileInputStream(file).getChannel()) {
            ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024);
            while (channel.read(buffer) != -1) {
                buffer.clear();
            }
        }
        
        return System.currentTimeMillis() - start;
    }
    
    private static long testDirectIO(File file) throws IOException {
        long start = System.currentTimeMillis();
        
        try (FileChannel channel = new FileInputStream(file).getChannel()) {
            ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 1024);
            while (channel.read(buffer) != -1) {
                buffer.clear();
            }
        }
        
        return System.currentTimeMillis() - start;
    }
    
    private static long getUsedDirectMemory() {
        try {
            Class<?> bitsClass = Class.forName("java.nio.Bits");
            Method method = bitsClass.getDeclaredMethod("reservedMemory");
            method.setAccessible(true);
            return (Long) method.invoke(null);
        } catch (Exception e) {
            return -1;
        }
    }
    
    private static String toMB(long bytes) {
        return String.format("%.2f", bytes / 1024.0 / 1024.0);
    }
}

/*
运行示例:
  java -XX:MaxDirectMemorySize=100m DirectMemoryDemo

输出:
  === 直接内存 vs 堆内存 ===
  
  Heap Buffer:
    isDirect: false
    在Java堆中分配
    IO时需要复制到本地内存
  
  Direct Buffer:
    isDirect: true
    在本地内存中分配
    IO时直接使用,零拷贝
  
  === IO性能对比 ===
  
  Heap Buffer IO: 1523ms
  Direct Buffer IO: 421ms
  
  直接内存快 3.6 倍
  
  最大直接内存: 100.00 MB
  已使用直接内存: 100.16 MB
*/

三、可运行Java代码示例

完整示例:JVM内存监控工具

import java.lang.management.*;
import java.util.*;
import java.util.concurrent.*;

/**
 * JVM内存监控工具
 * 综合监控各个内存区域的使用情况
 */
public class JVMMemoryMonitor {
    
    private final MemoryMXBean memoryMXBean;
    private final List<MemoryPoolMXBean> memoryPools;
    private volatile boolean running = true;
    
    public JVMMemoryMonitor() {
        this.memoryMXBean = ManagementFactory.getMemoryMXBean();
        this.memoryPools = ManagementFactory.getMemoryPoolMXBeans();
    }
    
    /**
     * 打印内存概览
     */
    public void printMemoryOverview() {
        System.out.println("╔════════════════════════════════════════════════════════════╗");
        System.out.println("║                    JVM 内存概览                             ║");
        System.out.println("╚════════════════════════════════════════════════════════════╝");
        
        // 堆内存
        MemoryUsage heapUsage = memoryMXBean.getHeapMemoryUsage();
        System.out.println("\n【堆内存 Heap】");
        printMemoryUsage(heapUsage);
        
        // 非堆内存(方法区等)
        MemoryUsage nonHeapUsage = memoryMXBean.getNonHeapMemoryUsage();
        System.out.println("\n【非堆内存 Non-Heap】");
        printMemoryUsage(nonHeapUsage);
        
        // 各内存池详情
        System.out.println("\n【内存池详情】");
        for (MemoryPoolMXBean pool : memoryPools) {
            System.out.println("\n  " + pool.getName());
            System.out.println("  ├─ 类型: " + pool.getType());
            System.out.println("  ├─ 内存管理器: " + 
                String.join(", ", pool.getMemoryManagerNames()));
            printMemoryUsage(pool.getUsage(), "  └─ ");
        }
    }
    
    /**
     * 打印内存使用情况
     */
    private void printMemoryUsage(MemoryUsage usage) {
        printMemoryUsage(usage, "");
    }
    
    private void printMemoryUsage(MemoryUsage usage, String prefix) {
        long used = usage.getUsed();
        long committed = usage.getCommitted();
        long max = usage.getMax();
        
        System.out.println(prefix + "已使用: " + formatBytes(used));
        System.out.println(prefix + "已提交: " + formatBytes(committed));
        System.out.println(prefix + "最大值: " + 
            (max == -1 ? "未定义" : formatBytes(max)));
        
        if (max != -1) {
            double usagePercent = (double) used / max * 100;
            System.out.println(prefix + "使用率: " + 
                String.format("%.2f%%", usagePercent));
            printUsageBar(usagePercent, prefix);
        }
    }
    
    /**
     * 打印使用率条形图
     */
    private void printUsageBar(double percent, String prefix) {
        int barLength = 40;
        int filled = (int) (percent / 100 * barLength);
        
        StringBuilder bar = new StringBuilder(prefix + "[");
        for (int i = 0; i < barLength; i++) {
            if (i < filled) {
                bar.append("█");
            } else {
                bar.append("░");
            }
        }
        bar.append("] ").append(String.format("%.1f%%", percent));
        System.out.println(bar);
    }
    
    /**
     * 格式化字节大小
     */
    private 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.0);
        return String.format("%.2f GB", bytes / 1024.0 / 1024.0 / 1024.0);
    }
    
    /**
     * 实时监控内存
     */
    public void startMonitoring(long intervalMs) {
        ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
        
        scheduler.scheduleAtFixedRate(() -> {
            if (!running) return;
            
            MemoryUsage heapUsage = memoryMXBean.getHeapMemoryUsage();
            double heapUsagePercent = (double) heapUsage.getUsed() / 
                heapUsage.getMax() * 100;
            
            System.out.printf("[%s] 堆内存: %.1f%% | 非堆: %s%n",
                new java.text.SimpleDateFormat("HH:mm:ss").format(new Date()),
                heapUsagePercent,
                formatBytes(memoryMXBean.getNonHeapMemoryUsage().getUsed()));
            
            // 警告
            if (heapUsagePercent > 80) {
                System.out.println("  ⚠️  警告: 堆内存使用率超过80%!");
            }
            
        }, 0, intervalMs, TimeUnit.MILLISECONDS);
        
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            running = false;
            scheduler.shutdown();
        }));
    }
    
    /**
     * 模拟内存使用
     */
    public void simulateMemoryUsage() {
        System.out.println("\n╔════════════════════════════════════════════════════════════╗");
        System.out.println("║                    内存使用模拟                             ║");
        System.out.println("╚════════════════════════════════════════════════════════════╝\n");
        
        List<byte[]> memoryHog = new ArrayList<>();
        Random random = new Random();
        
        try {
            for (int i = 0; i < 100; i++) {
                // 随机分配不同大小的对象
                int size = random.nextInt(1024 * 1024);  // 0-1MB
                memoryHog.add(new byte[size]);
                
                if (i % 10 == 0) {
                    System.out.println("分配了 " + (i + 1) + " 个对象");
                    MemoryUsage usage = memoryMXBean.getHeapMemoryUsage();
                    System.out.println("  堆内存使用: " + formatBytes(usage.getUsed()));
                }
                
                Thread.sleep(100);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } catch (OutOfMemoryError e) {
            System.out.println("\n❌ OutOfMemoryError: " + e.getMessage());
        }
        
        // 触发GC
        System.out.println("\n触发GC...");
        memoryHog.clear();
        System.gc();
        
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        
        MemoryUsage usage = memoryMXBean.getHeapMemoryUsage();
        System.out.println("GC后堆内存: " + formatBytes(usage.getUsed()));
    }
    
    /**
     * 线程栈信息
     */
    public void printThreadStackInfo() {
        System.out.println("\n╔════════════════════════════════════════════════════════════╗");
        System.out.println("║                    线程栈信息                               ║");
        System.out.println("╚════════════════════════════════════════════════════════════╝\n");
        
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        
        System.out.println("活动线程数: " + threadMXBean.getThreadCount());
        System.out.println("峰值线程数: " + threadMXBean.getPeakThreadCount());
        System.out.println("总启动线程数: " + threadMXBean.getTotalStartedThreadCount());
        System.out.println("守护线程数: " + threadMXBean.getDaemonThreadCount());
        
        // 线程详情
        System.out.println("\n线程列表:");
        for (ThreadInfo info : threadMXBean.dumpAllThreads(false, false)) {
            System.out.println("  - " + info.getThreadName() + 
                " [ID: " + info.getThreadId() + 
                ", State: " + info.getThreadState() + "]");
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        JVMMemoryMonitor monitor = new JVMMemoryMonitor();
        
        // 打印内存概览
        monitor.printMemoryOverview();
        
        // 打印线程信息
        monitor.printThreadStackInfo();
        
        // 启动实时监控(每2秒)
        System.out.println("\n开始实时监控(每2秒)...\n");
        monitor.startMonitoring(2000);
        
        // 模拟内存使用
        monitor.simulateMemoryUsage();
        
        // 持续运行一段时间
        Thread.sleep(10000);
        
        System.out.println("\n监控结束");
        System.exit(0);
    }
}

/*
运行示例:
  java -Xms100m -Xmx200m -XX:MaxMetaspaceSize=50m JVMMemoryMonitor
*/

四、实战应用场景

场景1:内存泄漏分析

import java.util.*;
import java.lang.ref.*;

/**
 * 内存泄漏场景与诊断
 */
public class MemoryLeakDemo {
    
    // 场景1:静态集合导致的内存泄漏
    static class StaticCollectionLeak {
        private static final Map<String, Object> CACHE = new HashMap<>();
        
        public void addToCache(String key, Object value) {
            CACHE.put(key, value);
            // 问题:只添加不删除,导致内存泄漏
        }
    }
    
    // 场景2:未关闭的资源
    static class ResourceLeak {
        public void readData() throws IOException {
            FileInputStream fis = new FileInputStream("data.txt");
            // 问题:未关闭流,导致native内存泄漏
            // 应该使用 try-with-resources
        }
        
        public void readDataCorrect() throws IOException {
            try (FileInputStream fis = new FileInputStream("data.txt")) {
                // 正确:自动关闭资源
            }
        }
    }
    
    // 场景3:监听器未注销
    static class ListenerLeak {
        private List<EventListener> listeners = new ArrayList<>();
        
        public void addListener(EventListener listener) {
            listeners.add(listener);
            // 问题:从未移除监听器
        }
        
        public void removeListener(EventListener listener) {
            listeners.remove(listener);
            // 解决:提供移除方法
        }
    }
    
    // 场景4:ThreadLocal未清理
    static class ThreadLocalLeak {
        private static final ThreadLocal<byte[]> threadLocal = 
            new ThreadLocal<>();
        
        public void processData() {
            threadLocal.set(new byte[1024 * 1024]);  // 1MB
            // 问题:线程池场景下,线程复用导致数据残留
            // 解决:finally中清理
        }
        
        public void processDataCorrect() {
            try {
                threadLocal.set(new byte[1024 * 1024]);
                // 处理数据
            } finally {
                threadLocal.remove();  // 清理
            }
        }
    }
    
    // 使用弱引用避免泄漏
    static class WeakReferenceCache {
        private final Map<String, WeakReference<Object>> cache = 
            new HashMap<>();
        
        public void put(String key, Object value) {
            cache.put(key, new WeakReference<>(value));
        }
        
        public Object get(String key) {
            WeakReference<Object> ref = cache.get(key);
            return ref != null ? ref.get() : null;
        }
    }
    
    // 内存泄漏诊断
    public static void main(String[] args) throws InterruptedException {
        System.out.println("=== 内存泄漏诊断示例 ===\n");
        
        StaticCollectionLeak leak = new StaticCollectionLeak();
        
        // 记录初始内存
        long initialMemory = Runtime.getRuntime().totalMemory() - 
            Runtime.getRuntime().freeMemory();
        
        // 模拟内存泄漏
        for (int i = 0; i < 10000; i++) {
            leak.addToCache("key" + i, new byte[1024]);
        }
        
        // 触发GC
        System.gc();
        Thread.sleep(1000);
        
        // 检查内存变化
        long afterGC = Runtime.getRuntime().totalMemory() - 
            Runtime.getRuntime().freeMemory();
        
        System.out.println("初始内存: " + (initialMemory / 1024 / 1024) + " MB");
        System.out.println("GC后内存: " + (afterGC / 1024 / 1024) + " MB");
        System.out.println("内存增长: " + ((afterGC - initialMemory) / 1024 / 1024) + " MB");
        
        if (afterGC > initialMemory * 2) {
            System.out.println("\n⚠️ 可能存在内存泄漏!");
            System.out.println("诊断步骤:");
            System.out.println("  1. 使用 jmap -histo <pid> 查看对象分布");
            System.out.println("  2. 使用 jvisualvm 或 MAT 分析堆转储");
            System.out.println("  3. 查找大对象和GC Root路径");
        }
    }
}

场景2:OOM应急处理

import java.util.*;
import java.lang.management.*;

/**
 * OOM应急处理工具
 */
public class OOMHandler {
    
    /**
     * 注册OOM处理器
     * 注意:仅用于应急,不能保证一定能执行
     */
    public static void registerOOMHandler() {
        // 方法1:捕获OutOfMemoryError
        Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
            if (throwable instanceof OutOfMemoryError) {
                handleOOM((OutOfMemoryError) throwable);
            }
        });
        
        // 方法2:使用MXBean监控
        MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
        
        // 方法3:预分配应急内存
        final byte[] emergencyBuffer = new byte[10 * 1024 * 1024];  // 10MB
        
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.out.println("\n=== OOM 应急处理 ===");
            System.out.println("正在生成诊断信息...");
            
            // 释放应急内存
            // emergencyBuffer = null;
            
            // 生成诊断信息
            generateDiagnosticReport();
        }));
    }
    
    /**
     * 处理OOM
     */
    private static void handleOOM(OutOfMemoryError error) {
        System.out.println("\n╔════════════════════════════════════════════════════════════╗");
        System.out.println("║                    OutOfMemoryError                        ║");
        System.out.println("╚════════════════════════════════════════════════════════════╝");
        
        String message = error.getMessage();
        
        if (message.contains("Java heap space")) {
            System.out.println("\n类型: 堆内存溢出");
            System.out.println("原因:");
            System.out.println("  1. 对象过多,内存不足");
            System.out.println("  2. 存在内存泄漏");
            System.out.println("  3. 堆大小设置不合理");
            System.out.println("\n解决方法:");
            System.out.println("  - 增大堆内存: -Xmx");
            System.out.println("  - 分析内存泄漏");
            System.out.println("  - 优化对象使用");
            
        } else if (message.contains("Metaspace")) {
            System.out.println("\n类型: 元空间溢出");
            System.out.println("原因:");
            System.out.println("  1. 加载类过多");
            System.out.println("  2. 动态代理、CGLib生成类过多");
            System.out.println("  3. 元空间设置过小");
            System.out.println("\n解决方法:");
            System.out.println("  - 增大元空间: -XX:MaxMetaspaceSize");
            System.out.println("  - 检查类加载器泄漏");
            System.out.println("  - 限制动态类生成");
            
        } else if (message.contains("GC overhead limit exceeded")) {
            System.out.println("\n类型: GC时间过长");
            System.out.println("原因:");
            System.out.println("  应用花费了过多时间在GC上但回收很少");
            System.out.println("\n解决方法:");
            System.out.println("  - 增大堆内存");
            System.out.println("  - 优化对象生命周期");
            System.out.println("  - 禁用此限制: -XX:-UseGCOverheadLimit");
            
        } else if (message.contains("Direct buffer memory")) {
            System.out.println("\n类型: 直接内存溢出");
            System.out.println("原因:");
            System.out.println("  NIO直接内存使用过多");
            System.out.println("\n解决方法:");
            System.out.println("  - 增大限制: -XX:MaxDirectMemorySize");
            System.out.println("  - 及时释放DirectBuffer");
        }
        
        // 生成诊断报告
        generateDiagnosticReport();
    }
    
    /**
     * 生成诊断报告
     */
    private static void generateDiagnosticReport() {
        System.out.println("\n╔════════════════════════════════════════════════════════════╗");
        System.out.println("║                    诊断报告                                 ║");
        System.out.println("╚════════════════════════════════════════════════════════════╝\n");
        
        // JVM信息
        Runtime runtime = Runtime.getRuntime();
        System.out.println("【JVM内存】");
        System.out.println("  最大内存: " + toMB(runtime.maxMemory()) + " MB");
        System.out.println("  总内存: " + toMB(runtime.totalMemory()) + " MB");
        System.out.println("  空闲内存: " + toMB(runtime.freeMemory()) + " MB");
        System.out.println("  已用内存: " + 
            toMB(runtime.totalMemory() - runtime.freeMemory()) + " MB");
        
        // 内存池
        System.out.println("\n【内存池】");
        for (MemoryPoolMXBean pool : ManagementFactory.getMemoryPoolMXBeans()) {
            MemoryUsage usage = pool.getUsage();
            System.out.println("  " + pool.getName() + ": " + 
                toMB(usage.getUsed()) + " / " + 
                (usage.getMax() == -1 ? "无限制" : toMB(usage.getMax())) + " MB");
        }
        
        // 线程信息
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        System.out.println("\n【线程】");
        System.out.println("  活动线程: " + threadMXBean.getThreadCount());
        System.out.println("  峰值线程: " + threadMXBean.getPeakThreadCount());
        
        // 建议
        System.out.println("\n【排查建议】");
        System.out.println("  1. 生成堆转储: jmap -dump:format=b,file=heap.hprof <pid>");
        System.out.println("  2. 查看对象分布: jmap -histo <pid>");
        System.out.println("  3. 使用MAT分析: https://www.eclipse.org/mat/");
        System.out.println("  4. 检查GC日志: -Xlog:gc*:file=gc.log");
    }
    
    private static String toMB(long bytes) {
        return String.format("%.2f", bytes / 1024.0 / 1024.0);
    }
    
    public static void main(String[] args) {
        registerOOMHandler();
        
        System.out.println("OOM处理器已注册");
        System.out.println("模拟OOM将在3秒后开始...\n");
        
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        
        // 模拟OOM
        List<byte[]> list = new ArrayList<>();
        while (true) {
            list.add(new byte[1024 * 1024]);
        }
    }
}

五、总结与最佳实践

核心要点回顾

区域作用线程异常类型
程序计数器字节码行号私有
虚拟机栈方法调用栈私有StackOverflowError、OOM
本地方法栈Native方法私有StackOverflowError、OOM
Java堆对象存储共享OutOfMemoryError
方法区类信息、常量共享OutOfMemoryError
直接内存NIO缓冲共享OutOfMemoryError

最佳实践

  1. 合理设置内存参数

    # 生产环境推荐
    java -Xms4g -Xmx4g \          # 初始和最大堆相同,避免动态扩展
         -Xmn2g \                  # 新生代大小
         -XX:MetaspaceSize=256m \  # 元空间初始大小
         -XX:MaxMetaspaceSize=512m \ # 元空间最大大小
         -XX:+UseG1GC \            # 使用G1垃圾收集器
         -XX:MaxDirectMemorySize=1g \ # 直接内存限制
         -Xlog:gc*:file=gc.log     # GC日志
         -jar app.jar
    
  2. 避免内存泄漏

    • 及时关闭资源(try-with-resources)
    • 清理ThreadLocal
    • 注销监听器
    • 限制缓存大小
  3. 监控内存使用

    • 定期检查内存使用率
    • 设置内存告警阈值
    • 保留GC日志用于分析
  4. 栈深度优化

    • 避免过深的递归调用
    • 可适当增大-Xss参数
    • 考虑使用迭代替代递归
  5. 直接内存使用

    • 大文件IO使用DirectBuffer
    • 及时释放直接内存
    • 监控直接内存使用量

常见问题排查

问题现象可能原因排查方法
StackOverflowError递归太深检查代码逻辑,增大-Xss
Java heap space OOM内存不足或泄漏jmap分析,MAT诊断
Metaspace OOM类加载过多检查动态代理,增大元空间
Direct buffer OOMNIO使用过多增大MaxDirectMemorySize
GC overhead频繁GC效果差增大堆内存,优化对象

扩展阅读

  • 《Java虚拟机规范》:官方规范文档
  • 《深入理解Java虚拟机》:周志明著
  • JVM参数大全www.oracle.com/java/techno…
  • MAT工具:Eclipse Memory Analyzer
  • JVisualVM:JDK自带监控工具