对象实例化的几种方式
- new
- 变形一:Xxx的静态方法获取(比如说单例模式)
- 变形二:XxxBuilder/XxxFactory的静态方法(工厂模式)
- Class的newInstance():反射的方式,只能调用空参构造方法,权限必须是public
- Constructor的newInstance(Xxx):反射的方式,可以调用空参、有参构造方法,权限没有要求
- 使用clone():不调用任何构造器,需要当前类实现Cloneeable接口,实现clone()
- 使用反序列化:从文件、网络中获取一个对象的二进制流
- 第三方库Objenesis
从字节码看对象的创建步骤
- 检查对象对于的类是否加载、链接、初始化(涉及到类的父类的加载)
- 给对象分配内存空间:具体使用那种看使用的垃圾收集器所采用的算法
- 内存规整:指针碰撞
- 内存规整:需要维护一张列表,记录空闲的内存空间。空闲列表分配
- 处理并发安全问题
- 采用CAS失败重试,区域加锁保证更新的原子性
- 每个线程预先分配了一块TLAB
- 初始化分配到的空间:对对象的属性进行默认的初始化,设置属性类型的默认初始化值
- 设置对象头
- 执行<init>方法,显示初始化属性的值
对象内存布局
- 对象头
- 运行时元数据:如果创建的是一个数组,还要记录数组的长度
- 哈希值(HashCode)
- GC分代年龄
- 锁状态标志
- 线程持有的锁
- 偏向线程ID
- 偏向时间戳
- 类型指针:指向类元数据InstanceKlass,确定对象所属的类型
- 运行时元数据:如果创建的是一个数组,还要记录数组的长度
- 实例数据
- 对象真正存储的有效信息,包括程序代码中定义的各种类型的字段(包括从父类继承下来的和自己本身所拥有的)
- 规则
- 相同宽度(大小)的字段总是分配到一起
- 父类定义的变量会出现在子类之前
- 如果CompactFieIds的参数为true(默认为true):子类的窄变量有可能插入到父类变量的空隙
- 对齐填充:起到占位符的作用
-
由于HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是任何对象的大小都必须是8字节的整数倍。对象头部分已经被精心设计成正好是8字节的倍数(1倍或者2倍),因此,如果对象实例数据部分没有对齐的话,就需要通过对齐填充来补全
-
对象的定位访问
有两种方式:句柄访问、直接指针(HotSpot采用)
句柄访问
局部变量表保存指向堆中的句柄池的地址,由句柄分别指向实例池中的对象实例和方法区中的对象类型数据
直接指针
优缺点:
- 句柄访问
- 优点:如果对象的地址发送改变(比如空间整理),局部变量表保存的句柄不需要变化
- 缺点:占用堆内存空间,并且相对于直接指针多了一步
- 直接指针
- 优点:不占用堆内存空间,效率比句柄快
- 如果对象的地址发送改变,局部变量表中的值需要改变