JAVA运行时数据区
1、java程序执行过程
- 了解运行时区域时,先了解一下java程序的执行过程
2、运行时区域展示
- 运行时数据区域包括:程序计数器、虚拟机栈、本地方法栈、堆、方法区
-
内存区域展示图
-
各个区域关系展示图
-
多个线程进行调用时展示图
-
3、程序计数器(Program Counter Register)
- 程序计数器(pc)是比较小的一块内存空间,可以看作当前线程所执行的字节码的行号指示器。为了线程切换后能恢复到正确执行位置,每条线程都需要一个独立的程序计数器,各个线程之间互不影响,独立存储,我们成这块内存区域为“线程私有”的内存。
- 由于程序计数器中存储的数据所占空间的大小不会随程序的执行而发生改变,因此,对于程序计数器是不会发生内存溢出现象(OutOfMemory)。
4、虚拟机栈(Java Virtual Machine Stack)
-
虚拟机栈是线程私有的,生命周期与线程相同, 虚拟机栈是Java方法执行的内存模型。
-
每个线程会创建一个虚拟机栈,线程中可能会调用很多方法,每个方法被执行的时候,java虚拟机都会同步创建一个栈帧(Stack Frame),用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
1.栈帧(位于虚拟机栈中)
- 每个方法被执行的时候,java虚拟机都会同步创建一个栈帧(每个方法对应一个栈帧)
- 栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息
-
局部变量表(Local Variables Table)
- 局部变量表:是一组变量值的存储空间,用于存放方法参数和方法内部定义的局部变量, 对于基本数据类型的变量,则直接存储它的值,对于引用类型的变量,则存的是指向对象的引用
- 大小确定:java程序被编译为class文件时,在方法的code属性的max_locals的数据项中确认了该方法所需分配的局部变量表的最大容量,
- 单位:变量槽(Variable Slot)
-
操作数栈(Oper and Stack)
- 在方法刚执行之前,操作数栈是空的,在方法执行过程中。会有各式各样的字节码指令在操作数栈中读取和写入, 程序中的所有计算过程都是在借助于操作数栈来完成的。
- 大小确定:java程序被编译为class文件时,在方法的code属性的max_stacks的数据项中确定了操作数栈的最大深度,操作数栈的深度不会找过max_stacks 数据项中设定的最大值。
-
动态链接(Dynamic Linking)
-
动态链接是指向运行时常量池的引用: 方法执行的过程中有可能需要用到类中的常量,所以必须要有一个引用指向运行时常量。
-
动态链接是一个将符号引用解析为直接引用的过程。 java虚拟机执行字节码时,遇到一个操作码,操作码第一次使用一个指向另一类的符号引用,则虚拟机就必须解析这个符号引用。解析时需要执行三个基本的任务:
- 查找被引用的类(有必要的话就装载它,一般采用延时装载)。
- 将符号引用替换为直接引用,这样当再次遇到相同的引用时,可以使用这个直接引用,省去再次解析的步骤。
- 当java虚拟机解析一个符号引用时,class文件检查器的第四趟扫描确保了这个引用的合法
-
Class文件的常量值中存在大量的符号引用,这些符号引用一部分会在类加载阶段或者第一次使用的时候被转化为直接引用,称为静态解析。另外一部分将在每一次运行期间都转化为直接引用,这部分就称为动态链接
-
-
方法出口(Return Address)
- 方法出口也叫方法返回地址, 当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址。
- 一个方法执行后,不是返回(不管是否有返回值)就是异常
-
演示栈帧执行流程:执行结果i=8的过程
- 演示i=9的过程
2.栈帧的执行流程
3.invoke指令
-
invokestatic:调用静态方法
-
invokespecial:用于调用实例构造器< init>()方法、私有方法、父类方法
-
invokevirtual:用于调用所有虚方法(加上virtual关键字的方法)
-
invokeinterface:用于调用接口方法,会在运行时再确定一个实现该接口得对象
-
invokedynamic:先在运行时动态解析出调用点限定符号引用得方法,然后再执行该方法。
-
比如下列代码,可以看出执行了invokestatic指令
package jvm_class; public class Test8 { public static void main(String[] args) { Test8.m(); } public static void m(){ int i=8; } }
-
可见,使用了invokestatic指令调用了静态方法m()
-
5、本地方法栈(Native Method Stacks)
- 地方法栈与Java栈的作用和原理非常相似。区别只不过是Java栈是为执行Java方法服务的,而本地方法栈则是为执行本地方法(Native Method)服务的。在JVM规范中,并没有对本地方发展的具体实现方法以及数据结构作强制规定,虚拟机可以自由实现它。在HotSopt虚拟机中直接就把本地方法栈和虚拟机栈合二为一。
6、堆(Heap)
- java堆(Java Heap)是虚拟机所管理的内存中最大的一片区域。java堆是线程共有的,所有线程共享想这一片区域。
- java堆在虚拟机启动时创建,此内存唯一目的是存放对象实例,几乎所有的对象实例都在这里分配内存。 JDK1.7后,字符串常量池从永久代中剥离出来,存放在堆中 。
- 在Java中,程序员基本不用去关心空间释放的问题,Java的垃圾回收机制会自动进行处理。因此这部分空间也是Java垃圾收集器管理的主要区域。在JVM中只有一个堆。
- 关于垃圾回收机制,在GC中讲解
7、方法区(Method Area)s
-
方法区在JVM中也是一个非常重要的区域,它与堆一样,是被线程共享的区域。在方法区中,存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。方法区逻辑上属于堆的一部分,但是为了与堆进行区分,通常又叫“非堆”。
-
很多人习惯将方法区称为“永久代”,在方法区中有一个非常重要的部分就是运行时常量池,它是每一个类或接口的常量池的运行时表示形式,在类和接口被加载到JVM后,对应的运行时常量池就被创建出来。当然并非Class文件常量池中的内容才能进入运行时常量池,在运行期间也可将新的常量放入运行时常量池中,比如String的intern方法。不过自从JDK7之后,Hotspot虚拟机便将字符串常量池从永久代移到了堆中。
-
永久代(PermGen Space)——(1.8之前)
- 字符常量位于Permspace——(1.7之前)
- FGC就不会清理
- 大小启动指定,不能清理
-
元空间(Meta Space)——(1.8之后)
- 字符串常量位于堆(1.7之后)
- 会触发FGC清理
- 不设定的话,最大就是内存占满
8、运行时常量池(Runtime Contant Pool)
-
运行时常量池是方法区的一部分。Class文件中有一项信息是常量池表(Constant Pool Table),用于存放编译期生成的符号引用,这部分内容将在类加载后存放在方法区的运行时常量池中。
-
类加载到内存中后,jvm就会将class常量池中的内容(字面量、符号引用)存放到运行时常量池中 ,在解析阶段,会把符号引用替换为直接引用,解析的过程会去查询字符串常量池
-
注意:与字符串常量池区分,运行时常量值存的是引用,位于方法区中。字符常量池位于堆中(JDK7之后)。