目录
Java内存区域
JVM内存结构分为五大区域:程序计数器、虚拟机栈、本地方法栈、方法区、堆
一、线程私有区域
这些区域是每个线程独有的,线程之间互不影响
1. 程序计数器(PC寄存器)
程序计数器是每个线程私有的,用于记录当前线程正在执行的字节码指令的地址(行号指示器)。
每个线程在执行 Java字节码时,JVM 会为其分配一个 程序计数器
这个计数器的值指向当前线程所执行的字节码指令的地址。
JVM 是多线程的,采用线程轮流切换执行的方式(线程间上下文切换),而程序计数器就起到“记住上次执行到哪”的作用。
特点:
- 每个线程一个,线程私有
- 唯一一个不会发生内存溢出(OutOfMemoryError)的内存区域:程序计数器占用空间小,只记录当前线程执行的字节码指令地址,不参与复杂内存操作,每个线程一个,也不需要频繁分配/释放内存,所以它是 JVM 中唯一一个不会出现 OOM 的内存区域。
作用:
- 控制程序执行流程
- 支持多线程切换恢复执行位置
2. 虚拟机栈
每个方法执行时会创建一个栈帧压入虚拟机栈,执行结束后弹出,栈帧用于存储:
- 局部变量表(基本数据类型、引用类型)
- 操作数栈:用于执行指令时临时存储操作数和计算结果
- 动态链接:用于将符号引用转为实际调用的方法地址
- 符号引用:符号引用是 Java 字节码中对类、字段、方法等实体的一种“名称级”描述,在类加载阶段将其解析为直接引用 —— 也就是在内存中的真实地址
- 直接引用:在程序运行期间,JVM 根据符号引用解析出来的具体内存地址 或者对内存中某个对象、类、方法、字段等的直接访问方式
- 也就是说符号引用是通过类名方法名等间接指向内存地址,直接引用是直接指向内存地址
- 方法返回信息:用于方法执行完毕后返回调用者位置,恢复现场
作用:
- 方法调用与执行的内存模型
特点
- 线程私有
- 生命周期随线程而定
- 可能抛出异常:
StackOverflowError
:如果栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出StackOverFlowError
错误。OutOfMemoryError
:如果栈的内存大小可以动态扩展, 那么当虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError
异常
3. 本地方法栈
类似于 Java 虚拟机栈,只不过执行的是 Native 方法
可能抛出 OOM 或 StackOverflowError
作用
- 为 JVM 使用的 Native 本地方法服务(一般由其他语言C、C++编写)
特点
类似于 Java 虚拟机栈,只不过执行的是 Native 方法
可能抛出 OutOfMemoryError 或 StackOverflowError
二、线程共享区域
这些区域是所有线程共享的,在 JVM 启动时创建。
4. 堆
Java 堆是 JVM 中内存最大的一块区域,用于存储所有对象实例和数组,是垃圾回收器(GC)管理的主要区域
结构划分
Java 堆通常被划分为 新生代(Young Generation) 和 老年代(Old Generation),有些实现还有 元空间(Metaspace)。
1. 新生代
- 存放新创建的对象
- Eden 区:新对象最初分配在这里
- Survivor 区:在一次新生代垃圾回收后,如果对象还存活,幸存对象会进入Survivor区
2. 老年代
- 存放从新生代“晋升”来的长生命周期对象
- GC 不频繁,但回收代价高
作用
- 存放所有对象实例、数组,几乎所有
new
出来的对象都在这里
特点
- 所有线程共享
- 垃圾收集器(GC)主要工作区域
- 按功能划分:
- 新生代(Young Generation)
- 老年代(Old Generation)
- 元空间(在 JDK8 前叫永久代)
- 可通过
-Xms -Xmx
调整堆大小
可能抛出: OutOfMemoryError
字符串常量池
字符串常量池 是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。
JDK1.7 之前,字符串常量池存放在永久代。JDK1.7 字符串常量池和静态变量从永久代移动到了 Java 堆中。
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2); // 输出 true,指向同一个池中的对象
String s3 = new String("hello");
System.out.println(s1 == s3); // 输出 false,new创建了新的对象,引用不同
5. 方法区
方法区是 JVM 内存结构的一部分,用于存储类的元信息,包括类结构、方法、字段、常量、静态变量等
- 在 JDK 1.7 及之前,它的实现依赖于 永久代(PermGen)
- 在 JDK 1.8 及之后,方法区实现被替换为 元空间(Metaspace),并从 JVM 内存中移出,改用本地内存分配
运行时常量池
运行时常量池是方法区的一部分,它存储的是编译期生成的各种字面量(如字符串字面量)和符号引用,运行时解析后变成直接引用。
1. 存储编译期生成的常量
- 包括类、方法、字段的符号引用(如类名、方法名、描述符等)
- 编译时生成的各种字面量(如字符串字面量、整数字面量等)
这些内容最初在 .class
文件中存在于常量池表,JVM 类加载时会将它们加载进运行时常量池。
2. 支持符号引用 → 直接引用的动态解析
运行时常量池负责将符号引用(如“java/lang/String”)解析为直接引用(内存地址)。
这正是 动态链接 的过程。
例如:
String s = new String("abc");
"abc"
是字面量,存在常量池。String
的类符号引用也来自常量池,运行时解析为java.lang.String
的真实内存地址。
3. 支持字符串常量池机制
- 所有字符串字面量都会存储在运行时常量池中,便于复用,节省内存。
- 通过
intern()
方法也可以手动将字符串加入常量池。
三、本地内存和直接内存
本地内存
指JVM管理之外的系统内存,比如:
- 操作系统分配的直接内存(Direct Memory)
- JNI调用中使用的内存
- JVM自身运行时的代码缓存、线程栈等操作系统内存
这部分内存不受JVM垃圾回收控制,由操作系统管理。
直接内存
- 直接内存是不属于Java堆(Heap)的内存区域,位于操作系统的本地内存中。
- 直接内存的优势是避免了Java堆和操作系统内核之间的内存拷贝,提高了I/O操作的效率
在jdk1.8中
Java程序内存 = JVM内存 + 本地内存
本地内存 = 元空间 + 直接内存