jvm-对象的实例化
对象的创建方式
- new
- Class的newInstance()
- Constructor的newInstance()
- 使用clone
- 使用反序列化
- 第三方库Objenesis
创建对象的步骤
- 判断对象对应的类是否加载、连接、初始化:虚拟机遇到一条new指令,首先去检查这个指令的参数能否在方法区常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、连接和初始化。如果没有,则在双亲委派模式下查找对应的class文件,如果没有找到则抛出异常,否则生成对应的class对象
- 为对象分配内存:计算对象占用空间的大小,接着在堆中划分一块内存给新对象。如果实例成员变量是引用变量,仅分配引用变量空间即可,即4个字节
- 如果内存规整:指针碰撞(意思是把所有用过的内存放在一边,空闲的放在另一边,中间放一个指针作为分界指示器。分配内存就把指针像空闲那边移动一段与对象大小相等的距离罢了)
- 如果内存不规整:空闲列表(意思是维护一个列表,纪录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的内容)
- 处理并发安全问题
- 采用cas配上失败重试保证更新的原子性
- 每个线程预先分配一块tlab
- 初始化分配到的空间:所有属性设置默认值,保证对象实例字段在不赋值时可以直接使用
- 设置对象的对象头:将对象的所属类、对象的hashcode和对象的gc信息、锁信息等数据存储在对象的对象头中。这个过程的具体设置由jvm实现
- 执行init方法进行初始化:属性的显示初始化、代码块中的初始化、构造器初始化
内存布局
对象头
- 运行时元数据
- 哈希值(hashcode)
- gc分代年龄
- 锁状态标志
- 线程持有的锁
- 偏向线程id
- 偏向时间戳
- 类型指针:指向类方法区,确定该对象所属的类型
如果是数组,还需要纪录数组的长度
实例数据
对象真正存储的有效信息,包括代码中对应的各种类型的字段(包括从父类继承的和本身的)
规则:
- 相同宽度的字段总是被分配在一起
- 父类中定义的变量会出现在子类之前
- 如果CompactFields参数为true(默认为true):子类的窄变量可能插入到父类的变量空隙
对齐填充
不是必须的,也没有特别含义,仅仅起到占位符的作用
对象访问
- 句柄访问:在堆中维护了一个句柄池,利用地址信息,可用在池中找到对象实例数据的指针和对象类型数据的指针
- 直接指针(hotspot采用):地址直接指向对象实体,实体中有一个类型指针指向方法区中的对象类型