【JVM】JVM的内存结构是怎样的?
JVM的内存结构是Java程序运行时内存管理的核心,不同区域有明确的职责。
一、整体划分
包括两大部分,分为线程私有区域(随线程创建/销毁,无需垃圾回收)和线程共享区域(所有线程共用,需要垃圾回收管理)。
- 线程私有区域:程序计数器、虚拟机栈、本地方法栈。 支撑线程独立执行(如方法调用、指令跟踪)
- 线程共享区域:堆、方法区(元空间)。 存储对象实例、类信息等全局数据,是内存管理的核心区域。
二、各区域详解:
1.程序计数器(Program Counter Register):
- 线程私有: 每个线程都有独立的程序计数器,互不干扰。
- 作用:记录当当前线程 “下一条要执行的 JVM 字节码指令的地址”,为线程提供执行路标,确保线程在切换(如CPU调度被中断)后能够恢复到正确的执行位置。
若执行的是 Java 方法,计数器存储下一条要执行的字节码指令的地址;
若执行的是 Native 方法(本地方法,如 C/C++ 实现),计数器值为undefined。 因为Native方法的执行由操作系统控制,而非JVM,无需JVM记录指令地址。
特点:
1.内存空间极小,几乎不占用资源。
2.是JVM中唯一不会发生OutOfMemoryError(OOM) 的区域。
3.本质是线程执行的路标,确保线程切换(如CPU调度)后能恢复到正确执行的位置。
2.虚拟机栈:
- 线程私有:每个线程创建时,会分配一个虚拟机栈,生命周期与线程一致。
- 作用:记录方法调用的执行状态,每个方法调用时,会创建一个栈帧,栈帧入栈;方法执行完毕,栈帧出栈。
- 栈帧包含的内容:
1.局部变量表: 存储方法内的局部变量(如基本类型、对象引用),容量在编译期确定;
2.操作数栈:方法执行时的临时数据存储区(如计算a+b时,先将a和b入栈,再执行加法);
3.动态链接:指向方法区中该方法的符号引用(运行时转换为直接引用);
4.返回地址:方法执行完后,回到调用者的位置(如main方法调用func(),func()执行完需回到main的下一行)。
可能的异常:
1.StackOverflowError:线程请求的栈深度超过虚拟机允许的最大深度(如递归调用无终止条件);
2.OutOfMemoryError:虚拟机栈可动态扩展时,扩展失败(如创建过多线程,栈内存总需求超过系统内存)。
3.本地方法栈:
- 线程私有:与虚拟机栈功能类似,但专门为 Native 方法(非 Java 实现的方法)服务。
- 特点:
可能抛出StackOverflowError(栈深度超限)和OutOfMemoryError(内存不足),与虚拟机栈一致。
4.堆:
- 线程共享:JVM中内存占比最大的区域,几乎所有对象实例(包括数组)都在这里分配内存。
- 作用:存储对象实例,是**垃圾回收(GC)**的主要工作区域。
- 结构划分:
堆空间通常按照对象“存活时间”划分为以下区域:- 新生代:存放新创建的对象,分为:
- Eden区(伊甸园):新对象优先分配到这里。
- Survivor 区(幸存者区):分为 From 和 To 两个大小相等的区域,用于存放 Eden 区回收后存活的对象(每次 GC 后,存活对象在 From 和 To 之间转移,经过多次存活后进入老年代)。
- 老年代:存放存活时间长的对象(如经过多次年轻代 GC 仍存活的对象)。
- 新生代:存放新创建的对象,分为:
特点:
1.堆的大小可通过**-Xms(初始堆大小)和-Xmx(最大堆大小)参数配置(尽可能两者设为一致,避免动态扩展消耗性能);
2.是OOM 最常见的区域**(如创建对象过多且无法被 GC 回收,堆空间耗尽时抛出java.lang.OutOfMemoryError: Java heap space)。
5.方法区:
- 线程共享:存储已被 JVM 加载的类信息、常量、静态变量、即时编译器(JIT)编译后的代码等数据。
- 历史演进:
- JDK 7 及之前:方法区的实现称为 “永久代(PermGen)”,属于堆的一部分,大小固定(易 OOM);
- JDK 8 及之后:永久代被元空间(Metaspace) 取代,元空间不再使用堆内存,而是直接使用本地内存(Native Memory),大小受系统内存限制(更灵活)。
- 运行时常量池:
是方法区的一部分,存放编译期生成的符号引用和字面量(如字符串常量"abc")。
三、各区域的协作关系:
Java 程序运行时,内存流转大致如下:
1.类加载器将类信息加载到方法区(元空间);
2.主线程启动,创建虚拟机栈,main方法的栈帧入栈;
3.执行new Object()时,在堆中创建对象实例,栈帧的局部变量表存储该对象的引用(地址);
4.程序计数器记录当前执行的字节码指令地址,确保线程切换后能继续执行;
5.方法执行完毕,栈帧出栈;对象不再被引用时,由 GC 在堆中回收。