一. 对象实例化相关
- 有哪些方式可以创建一个对象
- new 方式创建一个对象,由new方式创建对象又延伸出 Builder建造者方式,Factory工厂方 等静态方法方式
- Class的newInstance(),反射方式,要求只能调用孔灿构造器,权限必须是public
- Constructor的newInstance(),基于反射方式,可以调用空参,带参,并且权限没要求,实用性更广
- clone()方式,当前类要实现Cloneable接口,实现clone(),注意默认是浅拷贝
- 反序列化方式, 基于文件,网络,读取二进制流然后反序列化
- 基于第三方库的Objenesis利用asm字节码技术动态生成Constructor对象
创建对象的步骤
创建对象的步骤简述:
- 首先判断对象是否被加载
- 为对象分配内存
- 处理并发安全问题
- 初始化分配到的空间
- 设置对象的对象头
- 执行init方法进行初始化
- 首先判断对象是否被加载: 虚拟机接收到new指令,先检查这个指令的参数能否在Metaspace的常量池中定位到当前需要new类的符号引用,也可以理解为检查常量池中是否有对应这个对象的类,既判断类元信息是否存在
- 如果没有,那么在双亲委派模式下使用当前类加载器以ClassLoader+包名+类名为key查找对应的.class文件
- 如果没找到对应的.class文件则抛出ClassNotFoundException异常
- 如果找到了则进行加载生成对应的Class类对象
- 为对象分配内存空间: 也就是计算对象占用空间的大小,在堆中开辟内存空间,如果实例成员变量是引用类型,仅分配引用变量空间,既4个字节,分配内存空间时有两个注意点:指针碰撞与空闲列表
- 指针碰撞: 如果内存规整使用指针碰撞,意思就是当所有用过的内存与未使用的内存时规整区分开的,通过中间的一个指针作为分界点的指示器,分配内存就是把指针想空闲那边挪动一段与对象大小相等的距离,如果垃圾收集器使用压缩算法(也就是没有垃圾碎片时),虚拟机采用这种分配方式
- 空闲列表: 如果内存不规整有碎片时,虚拟机要维护一个列表,使用这个空闲列表进行分配, 也就是当有内存碎片时以使用的内存与未使用的内存相互交错,虚拟机通过一个列表记录哪些内存块时可用的,分配时从列表中找到一款足够大的空间分配给对象实例,然后更新列表,CMS采用这种算法
- 处理并发安全问题: 创建对象是非常频繁的,虚拟机需要保证并发问题,采用了CAS, TLAB两种方式(也就是多个线程同时操作堆空间,防止出现并发问题,例如两个线程同时修改堆内存中同一个位置的数据,通过CAS或TLAB来保证安全问题,一个是基于cas重试,一个是不同线程操作不同位置)
- CAS: 通过失败重试, 区域加锁, 保证指针更新操作的原子性
- TLAB: 把内存分配的动作按照线程划分在不同的空间进行,既美观线程在java堆中预先分配一小块内存,称为本地线程分配缓冲区,虚拟机是否使用TLAB,可以通过"-XX:+/-UseTLAB"来设置
- 初始化分配到的空间: 在内存分配结束后,虚拟机将分配到的内存空间都初始化为0(不包括对象头),保证对象的实例字段在java代码中可以不用付初始化值可以直接使用,程序能方位到这些字段的数据类型对应的零值,也就是对对象布局中指的实例数据进行初始化
- 设置对象的对象头: 对对象头中GC信息,锁信息,HashCode,对齐填充等信息进行初始化设置
- 执行init方法进行初始化: 初始化成员变量,执行实例化代码块静态成员初始化, 调用类的构造方法,并把堆内对象的首地址值赋值给引用变量,
二. 对象的内存布局
- 对象头,实例数据, 对齐填充 具体查看"JUC 对象布局"笔记
三. 对象的访问定位
- 上面我们将了创建对象,而创建对象的目的就是为了获取对象,使用它,
- 对象访问方式有虚拟机实现而定,有两种方式: 使用句柄访问, 直接使用指针访问
- 直接使用指针访问: 引用中存储的就是对象的地址,通过这个地址获取到堆中对象实例
- 使用句柄访问: 使用句柄访问时堆中会划分出一块内存被称为句柄池,栈中引用存放的对象的句柄池地址,句柄池中存放着对象实例与类型数据各地址信息,然后再通过这两个地址值获取到对象实例与类型, 使用句柄池的好处: 当对象被移动时例如垃圾回收移动对象,只需要改变句柄中实例数据指针即可,引用中的地址值不需要修改
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)