-
程序计数器(Program Counter Register):
程序计数器
是一块较小的内存空间,可以看作是当前线程所执行的字节码
的行号指示器.
由于Java虚拟机的多线程是通过线程轮流转换并分配处理器执行时间的方式来实现的,所以每条线程都需要有一个独立的程序计数器. -
Java虚拟机栈(Java Virtual Machine Stacks):
线程私有,生命周期与线程相同.
-
描述Java方法执行的内存模型:
每个方法执行时都会创建一个栈帧,用于存放
局部变量表
,操作数栈
,动态链接
,方法出口
等.
每个方法从调用到完成,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程.
-
-
本地方法栈(Native Method Stack):
本地
方法栈
与Java虚拟机栈
类似,不过它是服务于虚拟机用到的Native方法
.
有的虚拟机将本地方法栈
与虚拟机栈
合二为一(Sun HotSpot).什么是
Native方法
?
Native方法就是一个非java语言实现的方法
-
Java堆(Java Heap):
Java堆
是被所有线程共享的一块用于存放对象实例
的内存区域,在虚拟机启动时创建.一般情况下,所有的对象实例
和数组
都要在堆上分配.从垃圾回收的角度: 可以分为
新生代
和老年代
从内存分配的角度: 线程共享的堆可以划分出多个线程私有
的分配缓冲区
(TLAB). -
方法区(Method Area):
用于存储已被虚拟机加载的
类信息
,常量
(final),静态变量
(static),即时编译器编译后的代码
等数据.
这个区域的垃圾回收比较少,但并不是没有(主要针对常量池的回收
和类型的卸载
).-
运行时常量池(Runtime Constant Pool):
常量池(Constant Pool Table)
: Class文件中的一项描述信息,用于存放编译器生成的各种字面量
和符号引用
.运行时常量池
是方法区
的一部分.Class文件
中的常量池
信息会在类加载后存放在运行时常量池中
.
-
注意: 在虚拟机规范中,
方法区
是堆
的一个逻辑部分
,故Java堆
和方法区
同属于广义上的堆
.
注: 只针对普通Java对象,不包括数组和Class对象等.
-
虚拟机遇到
new指令
时会检查这个指令的参数能否在常量池中定位到一个类的符号引用
-
检查引用代表的类是否执行
类加载
过程(被加载
,解析
和初始化
过),若没有就执行相应过程. -
类加载
检查通过后,虚拟机就为新生对象分配空间.
-
并发情况下的线程安全解决方案:
-
对分配内存空间的动作进行
同步处理
. -
哪个线程需要分配内存,就在自己的
本地内存分配缓冲(TLAB)
上分配.TLAB
用完后才会同步锁定分配新的TLAB
.
-
-
分配完成之后,虚拟机将分配到的内存空间(包括实例属性)
初始化为零值
(可提前在TLAB分配时进行). -
执行
<init>
方法,按程序员的意愿初始化.
对象在内存中可分为三个区域: 对象头(Header)
,示例数据(Instance Data)
和对齐补充(Padding)
.
-
对象头:
对象头包含两部分信息:
Mark Word
: 用于存放对象自身的运行时数据
(哈希码
,GC分代年龄
,锁状态标志
,线程持有的锁
...).类型指针
: 指向该对象的元数据类型(应该是Class对象)
的指针,虚拟机通过它来确定这个对象是哪个类的实例.(若是数组还有一块用于几率数组长度)
-
示例数据:
对象真正存储的有效信息,也是程序代码中定义的各种类型的
字段内容
.无论是子类中定义的还是从父类继承的,都要记录起来. -
对齐填充:
对象的大小必须为
8字节的整数倍
,没有的话需要进行对齐填充
来补全(类似空格占位).
Java程序通过栈上的reference
引用来指向堆上的具体对象.
- 对象引用的定位方式:
-
句柄访问:
Java堆
上划分出一块内存作为句柄池
,reference
中储存的是对象的句柄地址
.而句柄中包含了对象实例数据地址
和对象类型数据地址
.优点:
reference
中储存的是稳定的句柄地址
,在对象移动时(垃圾收集时移动很普遍)只会改变句柄池中的实例数据指针
,reference
本身不用修改. -
直接指针访问:
reference
中储存对象地址
.而对象地址
中包含了对象实例数据
和对象类型数据地址
.优点: 速度更快,节省了一次指针定位的开销.