谈谈对Java虚拟机的认识(二)
运行时数据区
-
程序计数器:线程私有,记录当前线程的执行地址
①CPU需要不停的切换各个线程,这时候切换回来以后,就需要知道指令从哪开始继续执行;JVM的字节码解释器就需要通过改变PC寄存器的值来明确下一条应该执行什么样的字节码指令。(存储字节码指令地址的作用)
②所谓的多线程是,在一个特定的时间段内只会执行其中某一个线程的方法,CPU会不停的做任务切换,这必然导致经常中断和恢复,那怎样保证分毫无差呐-----因此,为了能够准确记录各个线程正在执行的当前字节码的指令地址,最好的办法就是每个线程都分配一个PC寄存器。(线程私有原因)
-
虚拟机栈:线程私有,生命周期和线程一致
①每个线程在创建时都会创建一个虚拟机栈,其内部保存着一个个栈帧,对应着一次次的Java方法调用。
②作用:主管Java程序的运行,保存方法的局部变量,部分结果,并参与方法的调用与返回。
③特点:
a. 栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器
b. JVM对JAVA栈的操作只有两个:每个方法执行,伴随着进栈,执行结束后的出栈
c. 对于栈来说,没有垃圾回收
④栈帧里存局部变量表,操作数栈,动态连接,方法返回地址和一些附加信息。每个方法从调用开始到执行完成的过程,都对应着一个栈帧在JVM里面从入栈到出栈的过程。一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体的虚拟机实现。在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧,与这个栈帧相关联的方法称为当前方法,执行引擎运行的所有字节码指令都只针对当前栈帧进行操作。
a. 局部变量表
一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。以变量槽slot为单位,一个slot可以放32位数据类型,对于long/double占用2个slot。
b. 操作数栈
用来存放操作数的栈结构,当一个方法刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令向操作数栈中写入和提取内容,也就是入栈和出栈的操作。(Java虚拟机的解释执行引擎称为基于栈的执行引擎,这个栈指的就是操作数栈)
c. 动态连接
运行时期将相关的符号引用转换为直接引用
d. 方法返回地址
方法执行完成的结果值
-
本地方法栈:为Native方法服务
-
堆: 存放对象实列和数组,可以处于物理上不连续的内存空间
-
方法区:存类信息,常量,静态变量。有运行时常量池,存放类的符号引用。
堆主要用来存放对象,栈主要用来执行程序
对象的创建
- 虚拟机遇到一条new指令时,会先去常量池检查能否找到new对应的类的符号引用。并检查这个类是否加载,初始化。
- 如果加载检查通过,则分配内存。分配内存有两种方式:①指针碰撞,针对连续内存区域;②空闲列表,针对不连续内存区域
- 内存分配完后,会对内存初始化0值,保证实例字段能在Java代码不赋初值也能使用。
- 接下来对对象信息进行设置,把类的元数据信息,对象的hash码,对象的GC分代年龄等信息存放在对象头之中。
- 最后执行用户的init().
对象的内存布局
三部分:对象头,实例数据,对齐填充
-
对象头:
①对象自身运行时的数据,如hash码,GC分代年龄,锁状态标志,线程持有锁等。
②类型指针,虚拟机通过这个来确定这个对象是哪个类的实例。
③如果对象是一个Java数组,那么对象头中还必须有一块用于记录数组长度的数据。
-
实例数据:对象真正存储的有效信息,也是在程序代码中定义的各种类型的字段内容。
-
对齐填充:JVM要求对象的起始地址必须是8字节的整数倍,因此当对象实例数据没有对齐时,这部分来补全。
对象的访问定位
- 取决于虚拟机的实现而定,有“句柄”和“直接指针”两种方式。
- “句柄”的好处是,在对象被移动(垃圾回收时很普遍),只用修改句柄中的实例数据指针,而reference本身不用修改。
- “直接指针”的好处是,速度更快,(因为节省了一次指针定位的时间开销,由于对象的访问在Java中非常频繁,因此这部分开销节省下来也很乐观)。
执行引擎
字节码文件即类文件被加载后,就能送入执行引擎了:
- 输入:字节码文件
- 处理:字节码解析
- 输出:执行结果
物理机的执行引擎是由硬件实现的,虚拟机的执行引擎是由自己实现的。