JVM 五大内存分区详解|从代码实例看懂运行时内存分布
2026/6/6 1:11:05 网站建设 项目流程

Java 程序运行时由 JVM 划分 5 大内存区域:方法区、虚拟机栈 (线程栈)、堆、程序计数器、本地方法栈,各区域分工不同、存储数据有明确边界,底层依托数组 + 指针边界实现内存划分。结合上图多线程代码案例,拆解各区域职责与数据存储逻辑。

一、JVM 内存分区总览

JVM 在程序启动后向操作系统申请内存,划分为 5 个独立区域,其中虚拟机栈、程序计数器、本地方法栈为线程私有(一个线程一套内存),堆、方法区是线程共享,所有线程共用同一块内存。内存底层本质依托数组实现,通过标记变量维护内存边界,管控数据存取。

二、逐个解析五大内存区

1. 方法区(线程共享)

存储内容

加载类后存放类的字节码文件、静态成员、运行时常量池(类常量池)、static 修饰的方法与变量

  1. 类常量池:类编译阶段生成,以 Map 结构存储字面量、符号引用,编译期常量存入此处,类加载后进入方法区;
  2. 静态存储区:所有被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. 虚拟机栈(线程栈|线程私有,一个线程对应一个独立栈)

存储内容

每个方法被调用时,会在栈中创建一块独立内存:栈帧,栈帧存放:局部变量、方法形参、方法返回地址。

核心特点
  1. 栈内存空间小、读写速度极快,遵循先进后出,底层基于数组实现;
  2. 方法执行完毕,对应栈帧立即出栈销毁,生命周期跟随线程;
  3. 易错点:A 方法调用 B 方法,两个方法的栈帧在同一线程栈中是并列结构,不是嵌套从属关系
结合代码举例
  1. 主线程 (main 线程):执行main()方法,main栈帧入主线程栈;后续调用Person.m2(),m2 栈帧继续入主线程栈,方法并列;

  2. 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(); } };
  1. 类加载阶段:Test、Person 字节码、静态方法m2()存入【方法区】;

  2. new Thread():Thread 对象、匿名内部类对象创建在【堆】;

  3. t1.start()

    • 主线程调用 start,start 底层 native 方法入【本地方法栈】;
    • 操作系统创建 t1 子线程,分配专属【线程栈 + 程序计数器】;
  4. t1 调度运行run()

    • run()栈帧入 t1【线程栈】;
    • Person.m2()静态方法:从方法区读取方法,m2 栈帧入 t1 栈;
    • m1()本类静态方法:同样生成栈帧入栈;
    • new Person():Person 实例创建在【堆】,局部变量xx(存对象地址)保存在 run 栈帧中;
    • xx.m1()成员方法:通过 xx 地址找到堆对象,m1 栈帧入 t1 线程栈;
  5. t2、t3、main 线程同理,各自占用私有栈,堆和方法区全线程共享。

四、补充总结

  1. 找数据口诀:对象存堆,局部变量存栈,类和静态放方法区,行号记在计数器,native 方法走本地栈
  2. 线程私有三区:栈、程序计数器、本地方法栈;共享两区:堆、方法区;
  3. 栈随方法销毁、堆靠 GC 回收、方法区随类卸载释放。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询