运行时数据区
JVM虚拟机运行java程序时所管理的内存区域,称为运行时数据区。
JVM内存
程序计数器
线程私有,用于记录当前线程正在执行字节码指令的地址。
有什么用呢?
CPU需要不停的切换各个线程,切换回来以后,就得知道接着从哪开始继续执行,所以需要程序计数器来记录指令地址。
栈
虚拟机栈
线程私有,用于保存方法中的数据,每调用一个方法会使用一个栈帧来保存数据。
栈帧
每个栈帧由局部变量表、操作数栈、帧数据组成。
局部变量表
局部变量表用于存放方法中的所有局部变量,最基本的存储单位是Slot(变量槽),long和double类型的数据会占用两个槽,其余占用一个槽。如果某个局部变量过了作用域,那么当前槽会复用。
操作数栈
用于存放中间数据。比如:
临时存放0和i+1的值,用于赋值给局部变量。
帧数据
包含动态链接、方法出口和异常表的引用
本地方法栈
线程私有,用于保存本地方法数据的栈。调用java中的native方法,方法会压入本地方法栈。
堆
线程共享,用于存放创建出的对象,栈存放是对象的引用,堆存放的是对象的实体。
为了进行高效的垃圾回收,虚拟机把堆内存逻辑上划分成三块区域(分代的唯一理由就是优化 GC 性能):
新生带(年轻代):新对象和没达到一定年龄的对象都在新生代
老年代(养老区):被长时间使用的对象,老年代的内存空间应该要比年轻代更大
元空间(JDK1.8 之前叫永久代):像一些方法中的操作临时对象等,JDK1.8 之前是占用 JVM 内存,JDK1.8 之后直接使用物理内存
方法区
方法区用于存储类信息、常量池、静态变量、JIT编译后的代码等数据,并没有规定如何去实现它,不同的厂商有不同的实现。而永久代是 Hotspot 虚拟机特有的概念, Java8 的时候又被元空间取代了,永久代和元空间都可以理解为方法区的落地实现。
类的元信息
类型信息
对每个加载的类型(类 class、接口 interface、枚举 enum、注解 annotation),JVM 必须在方法区中存储以下类型信息
- 这个类型的完整有效名称(全名=包名.类名)
- 这个类型直接父类的完整有效名(对于 interface或是 java.lang.Object,都没有父类)
- 这个类型的修饰符(public,abstract,final 的某个子集)
- 这个类型直接接口的一个有序列表
域(Field)信息
- JVM 必须在方法区中保存类型的所有域的相关信息以及域的声明顺序
- 域的相关信息包括:域名称、域类型、域修饰符(public、private、protected、static、final、volatile、transient 的某个子集)
方法(Method)信息
JVM 必须保存所有方法的
- 方法名称
- 方法的返回类型
- 方法参数的数量和类型
- 方法的修饰符(public,private,protected,static,final,synchronized,native,abstract 的一个子集)
- 方法的字符码(bytecodes)、操作数栈、局部变量表及大小(abstract 和 native 方法除外)
- 异常表(abstract 和 native 方法除外)
- 每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引
运行时常量池
一个 Java 源文件中的类、接口,编译后产生一个字节码文件。而 Java 中的字节码需要数据支持,通常这种数据会很大以至于不能直接存到字节码里,换另一种方式,可以存到常量池,这个字节码包含了指向常量池的引用。在动态链接的时候用到的就是运行时常量池。