理解JVM
JVM(Java 虚拟机)就像一个 "工作车间",里面划分了不同的区域,每个区域有专门的职责。下面用生活化的例子详细解释每个区域的作用:
1. 程序计数器(Program Counter Register)
- 作用:记录当前线程正在执行的代码行号,相当于 "书签"。
- 细节:
- 每个线程都有自己的程序计数器(线程私有),互不干扰
- 如果执行的是 Java 方法,记录的是当前字节码指令的地址
- 如果执行的是 native 方法(非 Java 代码),计数器值为 undefined
- 这是 JVM 中唯一不会 OutOfMemoryError 的区域
2. 虚拟机栈(VM Stack)
- 作用:存储方法调用的临时数据,相当于 "笔记本",记录方法执行时的变量和中间结果。
- 细节:
- 线程私有,每个方法调用时会创建一个 "栈帧"(Stack Frame)
- 栈帧包含:局部变量表(方法内定义的变量)、操作数栈(计算过程中临时存放数据)、方法返回地址等
- 方法调用时入栈,执行完出栈
- 常见问题:栈深度不够会抛出 StackOverflowError(比如递归调用太深);栈内存不足会抛出 OutOfMemoryError
3. 本地方法栈(Native Method Stack)
- 作用:和虚拟机栈类似,但专门为 native 方法(如调用操作系统底层功能的代码)服务。
- 细节:
- 线程私有,具体实现由虚拟机厂商决定
- 也可能抛出 StackOverflowError 和 OutOfMemoryError
4. 堆(Heap)
- 作用:存储 Java 中所有的对象实例和数组,是 JVM 中最大的一块内存区域,相当于 "仓库"。
- 细节:
- 所有线程共享,是垃圾回收(GC)的主要区域
- JDK 8 之后,字符串常量池也移到了堆中
- 堆内存不足时会抛出 OutOfMemoryError
- 现代 JVM 会把堆进一步细分:
- 新生代(Young Generation):新创建的对象先放这里,分为 Eden 区和两个 Survivor 区(From 和 To)
- 老年代(Old Generation):存活时间长的对象会移到这里
- 元空间(Metaspace,JDK 8 之后):替代永久代,存储类信息等(见下面说明)
5. 方法区(Method Area)
- 作用:存储类的元信息(如类结构、字段、方法、常量等),相当于 "档案库"。
- 细节:
- 所有线程共享,JDK 8 之前叫 "永久代",JDK 8 之后改名为 "元空间"(Metaspace)
- 存储内容包括:类的字节码、静态变量、常量池(如字符串常量)、方法信息等
- 元空间和永久代的区别:元空间使用本地内存,而不是 JVM 内存,默认情况下理论上受限于系统内存
- 内存不足时会抛出 OutOfMemoryError
6. 运行时常量池(Runtime Constant Pool)
- 作用:存储编译期生成的各种字面量和符号引用,相当于 "字典"。
- 细节:
- 是方法区的一部分
- 包含:字符串常量(如 "abc")、数字常量(如 123)、类和方法的引用等
- 具有动态性,运行时也能新增常量(如 String.intern () 方法)
总结记忆法
- 线程私有区(每个线程单独拥有):程序计数器、虚拟机栈、本地方法栈
- 线程共享区(所有线程共用):堆、方法区(元空间)、运行时常量池
可以把 JVM 想象成一个公司:
- 程序计数器 = 员工的工作进度表
- 虚拟机栈 = 员工的工作台(临时放正在处理的文件)
- 本地方法栈 = 外包团队的工作台
- 堆 = 公司的仓库(放所有货物)
- 方法区 = 公司的档案库(存规章制度、人员结构)
- 运行时常量池 = 档案库中的字典(快速查询常用信息)
这样每个区域的职责和特点就比较清晰了~