对象之生
1.对象的创建
1.1 当虚拟机遇到一条new指令时
a.判断该指令参数能否在常量池定位到一个类的符号引用;
b.符号引用代表的类是否已被加载 解析和初始化过。如果没有则执行类加载。
c.内存分配,对象所需的内存大小在类加载后就完全确定;
指针碰撞:java堆内存完全规整。
java堆为一块内存,中间一个指针作为分界点指示器,分配内存时将指针向空闲的内存那边挪动一段与对象大小相等的距离。
空闲列表:java堆不完全规整。
虚拟机维护一个列表,记录已使用的和空闲的内存。分配对象时在列表中找到一块足够大内存划分给对象。
d. 将对象分配的内存空间初始化为零值(不包括对象头)。若使用TLAB,此工作也可提前至TLAB分配时进行。
e. 设置对象头(类的元数据信息,哈希码,GC分代年龄等信息)。
f. 执行对象的 <init> 方法。
1.2 并发情况下内存分配
a. CAS+失败重试 的方式保证更新操作的原子性。
b. 将内存分配按照线程划分在不同的内存区域。
即每个线程先预分配一些内存,称为本地线程分配缓冲(TLAB)。各个线程使用各自的内存,当某个线程的内存不够使用时进行同步锁定。 通过-XX:+/-UseTLAB参数设定。
2. 对象的内存布局
2.1 基本布局
-
对象头
-
实例数据
-
对齐填充
2.2 对象头(运行时数据+类型指针)
运行时数据(Mark Word):哈希码,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏时间戳等。这部分在32位和64位虚拟机中分别为32bit和64bit。
类型指针:指向它的类元数据的指针。
普通的Java对象虚拟机可通过对象的元数据信息确定对象的大小,但是从数组的元数据信息中无法确定数组的大小. 所以
数组的对象头中还有一块记录数组长度的数据。
2.3 实例数据
对象存储的有效信息。也是在代码中定义的各种类型的字段内容.
相同宽度的字段总是被分配到一起.
父类定义的变量会出现在子类之前.
子类中较窄的变量也可能插入在父类变量的空隙之中.
存储顺序受分配策略和源码中的定义顺序影响。
2.4 对齐填充
并不是必然存在的,起到占位符的作用。
HotSpot虚拟机要求对象起始地址必须是8字节的整数倍,换句话说对象的大小必须是8字节的整数倍。
而头部分正好是8字节的倍数 (1倍或2倍),当实例数据没有对齐时,就需要通过对其填充来补全。
3. 对象定位方式(句柄和直接指针)
Java程序通过栈上的 reference 数据操作堆上的具体对象.通过 reference 定位到堆中对象的具体位置有两种方式.
3.1 句柄
若使用句柄访问。java堆划分一块内存作为句柄池。
reference 指向句柄,句柄包含指向对象类型数据和实例数据的指针。
reference 存储稳定的句柄地址,对象移动时只需要改变实例数据指针。而不改变 reference 本身
3.2 直接指针
reference 存储的对象地址。
可以直接访问到对象,通过对象找到其类型数据信息。 速度更快,节省了一次指针定位的时间开销。(HotSpot采用此方式)