1.对象的实例化
1.1 创建对象的方式
1.2 创建对象的步骤
从字节码角度看待对象的创建过程
public class ObjectTest {
public static void main(String[] args) {
Object obj = new Object();
}
}
0: new #2 // class java/lang/Object
3: dup
4: invokespecial #1 // Method java/lang/Object."<init>":()V
7: astore_1
8: return
1.3.1 判断对象对应的类是否加载、链接、初始化
- 虚拟机遇到一条new指令,首先去检查这个指令的参数能否在Metaspace的常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载,解析和初始化。(即判断类元信息是否存在)。
- 如果该类没有加载,那么在双亲委派模式下,使用当前类加载器以ClassLoader + 包名 + 类名为key进行查找对应的.class文件,如果没有找到文件,则抛出ClassNotFoundException异常,如果找到,则进行类加载,并生成对应的 Class 类对象。
1.3.2 为对象分配内存
- 首先计算对象占用空间的大小,接着在堆中划分一块内存给新对象。如果实例成员变量是引用变量,仅分配引用变量空间即可,即4个字节大小
- 如果内存规整:采用指针碰撞分配内存
- 如果内存不规整
1.3.3 处理并发安全问题
- 采用CAS+失败重试、区域加锁保证更新的原子性
- 每个线程预先分配TLAB — 通过设置 -XX:+/-UseTLAB参数来设置(区域加锁机制)
- 在Eden区给每个线程分配一块区域
1.3.4 初始化分配到的空间
1.3.5 设置对象的对象头
- 将对象的所属类(即类的元数据信息)、对象的HashCode和对象的GC信息、锁信息等数据存储在对象的对象头中。这个过程的具体设置方式取决于JVM实现。
1.3.6 执行init方法进行初始化
- 在Java程序的视角看来,初始化才正式开始。初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象的首地址赋值给引用变量
- 因此一般来说(由字节码中跟随invokespecial指令所决定),new指令之后会接着就是执行init方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完成创建出来。
从字节码角度看待init方法
/**
* 测试对象实例化的过程
* ① 加载类元信息 - ② 为对象分配内存 - ③ 处理并发问题 - ④ 属性的默认初始化(零值初始化)
* - ⑤ 设置对象头的信息 - ⑥ 属性的显式初始化、代码块中初始化、构造器中初始化
*
* 给对象的属性赋值的操作:
* ① 属性的默认初始化 - ② 显式初始化 / ③ 代码块中初始化 - ④ 构造器中初始化
*/
public class Customer{
int id = 1001;
String name;
Account acct;
{
name = "匿名客户";
}
public Customer(){
acct = new Account();
}
}
class Account{
}
0 aload_0
1 invokespecial #1 <java/lang/Object.<init>>
4 aload_0
5 sipush 1001
8 putfield #2 <cn/sxt/java/Customer.id>
11 aload_0
12 ldc #3 <匿名客户>
14 putfield #4 <cn/sxt/java/Customer.name>
17 aload_0
18 new #5 <cn/sxt/java/Account>
21 dup
22 invokespecial #6 <cn/sxt/java/Account.<init>>
25 putfield #7 <cn/sxt/java/Customer.acct>
28 return
2.对象的内存布局
2.1 对象头
对象头包含两部分:运行时元数据(Mark Word)和类型指针
- 运行时元数据
- 哈希值(HashCode),可以看作是堆中对象的地址
- GC分代年龄(年龄计数器)
- 锁状态标志
- 线程持有的锁
- 偏向线程ID
- 偏向时间戳
- 类型指针
- 指向类元数据InstanceKlass,确定该对象所属的类型。指向的其实是方法区中存放的类元信息
说明:如果对象是数组,还需要记录数组的长度
2.2 实例数据
- 说明
- 规则
2.3 对齐填充
不是必须的,也没特别含义,仅仅起到占位符的作用
2.4 内存布局总结
3.对象的访问定位
JVM是如何通过栈帧中的对象引用访问到其内部的对象实例呢?
3.1 句柄访问
-
优点:reference中存储稳定句柄地址,对象被移动(垃圾收集时移动对象很普遍)时只会改变句柄中实例数据指针即可,reference本身不需要被修改
-
缺点:在堆空间中开辟了一块空间作为句柄池,句柄池本身也会占用空间;通过两次指针访问才能访问到堆中的对象,效率低
3.2 直接指针(HotSpot采用)