JVM 五大内存分区详解|从代码实例看懂运行时内存分布

发布时间:2026/6/6 1:11:16

JVM 五大内存分区详解|从代码实例看懂运行时内存分布 Java 程序运行时由 JVM 划分 5 大内存区域方法区、虚拟机栈 (线程栈)、堆、程序计数器、本地方法栈各区域分工不同、存储数据有明确边界底层依托数组 指针边界实现内存划分。结合上图多线程代码案例拆解各区域职责与数据存储逻辑。一、JVM 内存分区总览JVM 在程序启动后向操作系统申请内存划分为 5 个独立区域其中虚拟机栈、程序计数器、本地方法栈为线程私有一个线程一套内存堆、方法区是线程共享所有线程共用同一块内存。内存底层本质依托数组实现通过标记变量维护内存边界管控数据存取。二、逐个解析五大内存区1. 方法区线程共享存储内容加载类后存放类的字节码文件、静态成员、运行时常量池类常量池、static 修饰的方法与变量。类常量池类编译阶段生成以 Map 结构存储字面量、符号引用编译期常量存入此处类加载后进入方法区静态存储区所有被static修饰的静态属性、静态方法统一存放正因如此静态方法无需实例化对象可直接通过类名.方法名()调用例代码中Person.m2()静态方法直接调用无需 new 对象。package JAVA算法锻炼; public class Test { // 本类静态方法存入方法区 public static void m1() { System.out.println(Test静态m1方法执行); } public static void main(String[] args) { // t1线程演示静态方法调用 new对象(堆) Thread t1 new Thread() { Override public void run() { Person.m2(); // 调用静态方法【方法区】 m1(); // 本类静态方法【方法区】 Person xx new Person(); // new对象在堆引用xx存在当前run栈帧 xx.m1(); // 实例方法 } }; // t2、t3简单线程 Thread t2 new Thread() { Override public void run() { System.out.println(我是线程2); } }; Thread t3 new Thread() { Override public void run() { System.out.println(我是线程3); } }; // 启动三个子线程 t1.start(); t2.start(); t3.start(); // 主线程调用静态方法 Person.m2(); System.out.println(我是主线程); } }结合代码举例Test类、Person类的字节码、Person.m2()静态方法、Test.m1()静态方法全部加载存入方法区程序首次运行时优先完成类加载字节码进入方法区。2. 虚拟机栈线程栈线程私有一个线程对应一个独立栈存储内容每个方法被调用时会在栈中创建一块独立内存栈帧栈帧存放局部变量、方法形参、方法返回地址。核心特点栈内存空间小、读写速度极快遵循先进后出底层基于数组实现方法执行完毕对应栈帧立即出栈销毁生命周期跟随线程易错点A 方法调用 B 方法两个方法的栈帧在同一线程栈中是并列结构不是嵌套从属关系。结合代码举例主线程 (main 线程)执行main()方法main栈帧入主线程栈后续调用Person.m2()m2 栈帧继续入主线程栈方法并列t1/t2/t3 三个子线程各自拥有专属独立线程栈t1 线程运行run()run()栈帧入 t1 专属栈内部依次调用Person.m2()、Test.m1()、new Person().m1()三个方法各自生成独立栈帧并入 t1 栈t2、t3 线程各自的run()方法分别入自己的私有栈。关键点new Person xx是局部变量存放在 t1 的 run 栈帧内xx引用地址指向堆中的 Person 实例。3. 堆线程共享JVM 最大内存区域存储内容所有new创建的对象实例、数组对象对象的成员属性全部存放在堆中对象的引用地址比如代码里Person xx保存在栈局部变量里。特点堆空间是五大分区中容量最大的区域GC 垃圾回收主要针对堆内存闲置对象会被 JVM 自动回收。结合代码举例t1线程中执行 new Person()Person 实体对象创建在堆内存栈中局部变量xx保存堆中对象的内存地址通过地址找到堆里的对象进而调用成员方法xx.m1()。4. 程序计数器线程私有最小内存区作用记录当前线程正在执行的字节码行号用来配合 CPU 切换线程当 CPU 时间片切换、线程暂停执行时程序计数器保存断点位置线程再次抢到 CPU 资源后依靠计数器记录的地址从上次暂停的代码继续执行。唯一没有 OOM 内存溢出的 JVM 内存区域。5. 本地方法栈线程私有和虚拟机栈结构几乎一致区别在于虚拟机栈服务 Java 代码本地方法栈服务 native 本地方法C/C 实现的底层方法Thread.start()底层调用 native 方法对应栈帧存入本地方法栈。三、结合示例代码完整执行流程// 代码片段 Thread t1 new Thread(){ public void run(){ Person.m2(); m1(); Person xx new Person(); xx.m1(); } };类加载阶段Test、Person 字节码、静态方法m2()存入【方法区】new Thread()Thread 对象、匿名内部类对象创建在【堆】t1.start()主线程调用 startstart 底层 native 方法入【本地方法栈】操作系统创建 t1 子线程分配专属【线程栈 程序计数器】t1 调度运行run()run()栈帧入 t1【线程栈】Person.m2()静态方法从方法区读取方法m2 栈帧入 t1 栈m1()本类静态方法同样生成栈帧入栈new Person()Person 实例创建在【堆】局部变量xx存对象地址保存在 run 栈帧中xx.m1()成员方法通过 xx 地址找到堆对象m1 栈帧入 t1 线程栈t2、t3、main 线程同理各自占用私有栈堆和方法区全线程共享。四、补充总结找数据口诀对象存堆局部变量存栈类和静态放方法区行号记在计数器native 方法走本地栈线程私有三区栈、程序计数器、本地方法栈共享两区堆、方法区栈随方法销毁、堆靠 GC 回收、方法区随类卸载释放。

相关新闻