JVM 内存结构速查:堆、栈、元空间、直接内存

📅 发布时间:2026/10/3 6:15:03
JVM 内存结构速查:堆、栈、元空间、直接内存
1. 引言JVM 的内存结构是 Java 开发者绕不开的基础知识也是面试中高频考察的重点。无论是排查 OOMOutOfMemoryError、优化 GC 停顿还是理解对象生命周期都离不开对内存区域的清晰认知。本文以「速查」为目标用最精炼的方式梳理 JVM 四大核心内存区域——堆、栈、元空间、直接内存——的作用、常见异常、关键区别与典型溢出案例帮助你快速建立整体框架并在实际排查中有的放矢。2. 各区域作用与常见异常JVM 内存按线程是否共享可分为两大类线程共享区堆、元空间/方法区与线程私有区虚拟机栈、本地方法栈、程序计数器。下面逐一说明。2.1 堆Heap作用存放对象实例与数组是 GC 管理的主要区域也是 JVM 内存中最大的一块。细分新生代Eden Survivor 0/1与老年代可通过-Xms/-Xmx控制大小。常见异常java.lang.OutOfMemoryError: Java heap space通常由对象过多、内存泄漏或堆设置过小引起。2.2 虚拟机栈Java Virtual Machine Stack作用每个线程私有存放栈帧局部变量表、操作数栈、动态链接、方法出口。每调用一个方法就压入一个栈帧。常见异常StackOverflowError栈深度超过-Xss限制典型场景是无限递归。OutOfMemoryError: unable to create new native thread线程数过多导致无法分配新栈空间。2.3 元空间Metaspace作用存放类的元数据类结构、方法信息、常量池等是 JDK 8 之后替代永久代PermGen的区域。位置使用本地内存Native Memory默认无上限可通过-XX:MaxMetaspaceSize限制。常见异常OutOfMemoryError: Metaspace常见于动态生成大量类如 CGLIB、反射、热部署且未回收。2.4 直接内存Direct Memory作用堆外内存由 NIO 的DirectByteBuffer等分配通过-XX:MaxDirectMemorySize控制默认等于堆大小。特点避免堆内与堆外之间的数据拷贝适合大块数据读写与网络传输。常见异常OutOfMemoryError: Direct buffer memory由直接内存分配过多且未释放引起。2.5 程序计数器Program Counter Register作用线程私有记录当前线程执行的字节码行号是唯一不会 OOM 的区域。3. 对象创建与逃逸分析3.1 对象创建流程一个 Java 对象从创建到回收大致经历以下步骤// 示例普通对象创建UserusernewUser(张三,25);类加载检查JVM 检查User类是否已加载、解析、初始化。分配内存在堆中为对象分配内存指针碰撞或空闲列表。内存空间初始化将分配到的内存空间初始化为零值。设置对象头记录对象的哈希码、GC 分代年龄、锁状态等信息。执行构造方法调用init方法完成初始化。3.2 逃逸分析逃逸分析是 JIT 编译器的一项优化技术用于判断对象是否「逃逸」出方法或线程作用域从而决定能否做以下优化栈上分配对象不逃逸时直接在栈帧上分配方法结束即销毁减少 GC 压力。标量替换将对象拆解为多个基本类型字段直接在栈上使用避免堆分配。同步消除对象不逃逸且未发生竞争时可去掉锁。// 逃逸分析示例对象未逃逸可栈上分配publicintsum(inta,intb){PointpnewPoint(a,b);// p 未逃逸出方法returnp.xp.y;}注意逃逸分析默认开启JDK 8 起默认-XX:DoEscapeAnalysis但栈上分配并非对所有场景都生效实际效果取决于 JIT 编译结果。4. 元空间和永久代区别永久代PermGen是 JDK 7 及之前方法区的实现元空间Metaspace是 JDK 8 起方法区的实现。两者核心区别如下对比项永久代PermGen元空间Metaspace所在位置JVM 堆内存内本地内存Native Memory默认大小有限受-XX:MaxPermSize限制无上限受物理内存限制溢出异常OutOfMemoryError: PermGen spaceOutOfMemoryError: Metaspace参数控制-XX:PermSize/-XX:MaxPermSize-XX:MetaspaceSize/-XX:MaxMetaspaceSize字符串常量池JDK 7 起已移至堆中仍在堆中与元空间无关类元数据回收受 Full GC 影响可独立触发类卸载回收更灵活为什么用元空间替代永久代永久代大小难以预估容易因类加载过多导致 OOM。永久代位于堆内与堆争抢内存GC 负担重。元空间使用本地内存默认不设上限更灵活且类元数据回收更独立。5. 直接内存溢出案例直接内存溢出通常发生在大量使用 NIO 或 Netty 的场景。下面给出一个典型触发案例。importjava.nio.ByteBuffer;importjava.util.ArrayList;importjava.util.List;publicclassDirectMemoryOOM{// 设置较小的直接内存上限便于复现-XX:MaxDirectMemorySize64mpublicstaticvoidmain(String[]args){ListByteBufferbuffersnewArrayList();inti0;while(true){// 每次分配 1MB 直接内存ByteBufferbufferByteBuffer.allocateDirect(1024*1024);buffers.add(buffer);// 持有引用防止被 GC 回收System.out.println(已分配 (i) MB 直接内存);}}}运行结果程序持续分配直接内存最终抛出Exception in thread main java.lang.OutOfMemoryError: Direct buffer memory原因分析ByteBuffer.allocateDirect()分配的是堆外内存不受堆大小限制。直接内存由Cleaner机制回收但若持有引用且未显式释放会持续累积直至超过-XX:MaxDirectMemorySize。直接内存溢出时堆可能仍有大量空闲因此排查时不能只看堆内存。排查建议检查是否大量使用allocateDirect且未释放。关注 Netty 等框架的PooledByteBufAllocator配置。通过-XX:MaxDirectMemorySize合理限制直接内存上限。使用jcmd、jmap或 NMTNative Memory Tracking分析堆外内存占用。6. 总结区域线程共享存放内容常见异常关键参数堆是对象实例、数组Java heap space-Xms/-Xmx虚拟机栈否栈帧、局部变量StackOverflowError-Xss元空间是类元数据Metaspace-XX:MaxMetaspaceSize直接内存否进程级堆外缓冲区Direct buffer memory-XX:MaxDirectMemorySize掌握 JVM 内存结构是定位线上 OOM、优化 GC 性能的第一步。建议结合jstat、jmap、jcmd等工具实际观察各区域的内存变化把理论落到实践中。