Skip to content

Java堆内存详解

核心概念

Java堆是JVM管理的内存中最大的一块,也是所有线程共享的内存区域,在虚拟机启动时创建。

堆的唯一目的就是存放对象实例,几乎所有的对象实例和数组都在这里分配内存。

不是"所有"对象都在堆上

随着JIT编译器和逃逸分析技术的成熟,一些优化技术可能改变对象的分配位置:

  • 栈上分配:如果对象未逃逸出方法,可以直接在栈上分配
  • 标量替换:将对象拆解为基本类型,直接在栈上分配

从JDK 1.7开始默认开启逃逸分析(-XX:+DoEscapeAnalysis)。

java
// 逃逸分析示例
public class EscapeAnalysisDemo {
    public static void main(String[] args) {
        for (int i = 0; i < 10000000; i++) {
            createUser();
        }
    }
    
    // user对象未逃逸出方法,可能在栈上分配
    private static void createUser() {
        User user = new User("张三", 25);
        System.out.println(user.getName());
    }
}

堆的分代结构

堆是垃圾收集器管理的主要区域,也称GC堆(Garbage Collected Heap)

由于现代垃圾收集器基本都采用分代收集算法,堆被划分为不同的区域:

mermaid
graph LR
    subgraph "堆内存"
        subgraph "新生代 1/3"
            Eden["Eden区<br/>80%"]
            S0["Survivor0<br/>10%"]
            S1["Survivor1<br/>10%"]
        end
        
        subgraph "老年代 2/3"
            Old["Old Generation"]
        end
    end
    
    style Eden fill:#50C878,stroke:#2E7D4E,stroke-width:2px,rx:10,ry:10
    style S0 fill:#87CEEB,stroke:#4A90E2,stroke-width:2px,rx:10,ry:10
    style S1 fill:#87CEEB,stroke:#4A90E2,stroke-width:2px,rx:10,ry:10
    style Old fill:#DDA0DD,stroke:#8B008B,stroke-width:2px,rx:10,ry:10

新生代(Young Generation)

新生代用于存放新创建的对象,分为三个区域:

Eden区(伊甸园区)

  • 占新生代的80%
  • 新对象首先在此分配
  • 触发Minor GC的主要区域

Survivor区(幸存者区)

  • 分为From和To两个区,各占10%
  • 存放经过一次GC后存活的对象
  • 采用复制算法

老年代(Old Generation)

老年代存放长期存活的对象:

  • 经过多次Minor GC仍存活的对象
  • 大对象直接进入老年代
  • 占堆的2/3

为什么要分代

分代的核心思想基于一个观察:大部分对象都是朝生夕死的

通过分代,可以为不同区域设置不同的GC策略,提升垃圾回收效率:

特点GC类型GC频率
新生代对象存活率低Minor GC频繁
老年代对象存活率高Major GC低频
mermaid
graph LR
    A["新对象"] --> B["Eden区分配"]
    B -->|"Minor GC"| C{"存活?"}
    C -->|"是"| D["Survivor区"]
    C -->|"否"| E["回收"]
    D -->|"年龄+1"| F{"年龄>=15?"}
    F -->|"是"| G["晋升老年代"]
    F -->|"否"| D
    
    style B fill:#50C878,stroke:#2E7D4E,stroke-width:2px,rx:10,ry:10
    style D fill:#4A90E2,stroke:#2E5C8A,stroke-width:2px,rx:10,ry:10
    style G fill:#E94B3C,stroke:#B8341F,stroke-width:2px,rx:10,ry:10

Minor GC与Full GC

Minor GC(新生代GC)

  • 发生在新生代
  • 触发条件:Eden区满
  • 速度快,频繁发生
  • 采用复制算法

Major GC/Full GC

  • 清理老年代(Major GC)或整个堆(Full GC)
  • 触发条件:老年代空间不足
  • 速度慢,STW时间长
  • 应尽量避免
java
// 触发Minor GC的示例
public class MinorGCDemo {
    public static void main(String[] args) {
        List<byte[]> list = new ArrayList<>();
        
        for (int i = 0; i < 1000; i++) {
            // 不断创建对象,Eden满时触发Minor GC
            byte[] data = new byte[1024 * 100]; // 100KB
            
            // 保留部分对象,模拟存活
            if (i % 10 == 0) {
                list.add(data);
            }
        }
    }
}

堆内存配置参数

基础配置

bash
# 设置堆的初始大小
-Xms512m

# 设置堆的最大大小
-Xmx2g

# 推荐:Xms和Xmx设置相同,避免堆扩容带来的性能损耗
-Xms2g -Xmx2g

新生代配置

bash
# 设置新生代大小
-Xmn512m

# 设置新生代和老年代的比例(默认1:2)
-XX:NewRatio=2

# 设置Eden和Survivor的比例(默认8:1:1)
-XX:SurvivorRatio=8

配置示例

bash
# 生产环境推荐配置
-Xms4g -Xmx4g           # 堆大小4G
-Xmn1536m               # 新生代1.5G
-XX:SurvivorRatio=8     # Eden:S0:S1 = 8:1:1
-XX:+UseG1GC            # 使用G1收集器

堆内存溢出

堆最容易出现OutOfMemoryError,表现形式有多种:

Java heap space

创建对象时堆空间不足。

java
public class HeapOOMDemo {
    public static void main(String[] args) {
        List<byte[]> list = new ArrayList<>();
        while (true) {
            // 不断创建1MB的数组,最终导致堆内存溢出
            list.add(new byte[1024 * 1024]);
        }
    }
}

// 运行参数: -Xms20m -Xmx20m
// 输出: java.lang.OutOfMemoryError: Java heap space

GC Overhead Limit Exceeded

JVM花费太多时间执行GC却只能回收很少的堆空间。

java
public class GCOverheadDemo {
    public static void main(String[] args) {
        Map<Integer, String> map = new HashMap<>();
        int i = 0;
        while (true) {
            // 不断添加数据,GC频繁但回收不了
            map.put(i++, String.valueOf(i).intern());
        }
    }
}

// 输出: java.lang.OutOfMemoryError: GC overhead limit exceeded

堆内存监控

使用JVM参数

bash
# 打印GC详情
-XX:+PrintGCDetails

# 打印GC时间戳
-XX:+PrintGCDateStamps

# GC日志输出到文件
-Xloggc:/path/to/gc.log

使用代码监控

java
public class HeapMonitor {
    public static void printHeapInfo() {
        Runtime runtime = Runtime.getRuntime();
        
        long maxMemory = runtime.maxMemory();      // 最大堆内存
        long totalMemory = runtime.totalMemory();  // 已分配堆内存
        long freeMemory = runtime.freeMemory();    // 空闲内存
        long usedMemory = totalMemory - freeMemory; // 已使用内存
        
        System.out.printf("Max: %d MB%n", maxMemory / 1024 / 1024);
        System.out.printf("Total: %d MB%n", totalMemory / 1024 / 1024);
        System.out.printf("Used: %d MB%n", usedMemory / 1024 / 1024);
        System.out.printf("Free: %d MB%n", freeMemory / 1024 / 1024);
    }
}

使用JMX监控

java
public class JMXHeapMonitor {
    public static void monitor() {
        MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
        MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
        
        System.out.printf("Init: %d MB%n", heapUsage.getInit() / 1024 / 1024);
        System.out.printf("Used: %d MB%n", heapUsage.getUsed() / 1024 / 1024);
        System.out.printf("Committed: %d MB%n", heapUsage.getCommitted() / 1024 / 1024);
        System.out.printf("Max: %d MB%n", heapUsage.getMax() / 1024 / 1024);
    }
}

堆与元空间的关系

JDK 8之后,永久代被**元空间(Metaspace)**取代:

mermaid
graph TB
    subgraph "JDK 7及之前"
        Heap7["堆内存"]
        PermGen["永久代<br/>(方法区实现)"]
    end
    
    subgraph "JDK 8及之后"
        Heap8["堆内存"]
        Meta["元空间<br/>(本地内存)"]
    end
    
    Heap7 --> PermGen
    Heap8 -.-> Meta
    
    style PermGen fill:#E94B3C,stroke:#B8341F,stroke-width:2px,rx:10,ry:10
    style Meta fill:#50C878,stroke:#2E7D4E,stroke-width:2px,rx:10,ry:10

变化点:

  • 元空间使用本地内存,不再占用堆内存
  • 字符串常量池从永久代移到堆中
  • 静态变量也移到堆中

堆内存优化建议

合理设置堆大小

bash
# 根据应用特点设置
# 响应优先型:较小堆,减少GC时间
-Xms1g -Xmx1g

# 吞吐量优先型:较大堆,减少GC频率
-Xms4g -Xmx4g

新生代与老年代比例

bash
# 短生命周期对象多:增大新生代
-XX:NewRatio=1  # 新生代:老年代 = 1:1

# 长生命周期对象多:增大老年代
-XX:NewRatio=3  # 新生代:老年代 = 1:3

选择合适的GC器

场景推荐GC参数
小内存、低延迟Serial-XX:+UseSerialGC
多核、高吞吐Parallel-XX:+UseParallelGC
低延迟、大内存G1-XX:+UseG1GC
超低延迟ZGC-XX:+UseZGC

理解Java堆内存的结构和工作原理,是进行JVM调优、解决内存问题的基础。

更新: 2025-12-06 15:11:58
原文: https://www.yuque.com/u22210564/zoxfmt/ys4rl28tfogidgom

Java 后端面试知识库