HotSpot 虚拟机对象
虚拟机对象的创建方式
- new
- Class的newInstance():反射的方式,只能调用空仓构造函数,权限必须是public
- Constructor的NewInstance(Xxx):反射的方式,可以调用空仓,有参构造函数,权限没有要求
- clone()不调用构造函数,当前类需要实现cloneable接口并实现clone()方法
- 使用反序列化:从文件或者网络获取二进制流
- 第三方库Objenesis
创建对象步骤
-
1.判断对象是否加载,连接,初始化:当虚拟机遇到一条new指令后先去检查这个指令所需要加载的类在元空间的常量池是否加载初始化,如果没有则在双亲委派机制下使用ClassLoader去当前src下查找是否有.class文件,没有就抛出ClassNotFoundException()异常,找到的话就进行加载。
-
2.为对象分配内存:首先计算对象占用空间大小,接着在堆里划分内存给对象,如果实例成员变量是引用类型,仅分配引用变量内存空间(String,object),即4个字节.此时在堆上分配内存又有几种情况,a,如果内存规整,那就使用指针碰撞的方式创建,即连续空间,b,不规整的时候,也就是内存碎片化的时候,这时虚拟机采用空闲列表法来分配内存。顾名思义,就是内存你维护一个列表记录哪些内存可用,在分配内存的时候,在列表里找到一个足够大的空间来存储对象实例,同时更新列表内容。
-
3.处理并发问题:都知道堆内存很大,分配内存也很频繁,所以很容易出现并发安全问题,虚拟机为我们提供了CAS失败重试,区域枷锁保证更新的原子性,和线程预先分配TLAB块两种方式解决。
-
4.初始化分配数据到内存空间:初始化数据,设置默认值。
-
5.设置对象头
-
6.正式初始化对象,调用方法,把堆内的对象地址关联到对象引用上。
对象的内存结构
在 HotSpot 虚拟机中,对象的内存布局分为以下 3 块区域:
- 对象头(Header)
- 实例数据(Instance Data)
- 对齐填充(Padding)
对象头
对象头记录了对象在运行过程中所需要使用的一些数据:
- 哈希码
- GC 分代年龄
- 锁状态标志
- 线程持有的锁
- 偏向线程 ID
- 偏向时间戳
对象头可能包含类型指针,通过该指针能确定对象属于哪个类。如果对象是一个数组,那么对象头还会包括数组长度。
实例数据
实例数据部分就是成员变量的值,其中包括父类成员变量和本类成员变量。
对齐填充
用于确保对象的总长度为 8 字节的整数倍。
HotSpot VM 的自动内存管理系统要求对象的大小必须是 8 字节的整数倍。而对象头部分正好是 8 字节的倍数(1 倍或 2 倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。
对齐填充并不是必然存在,也没有特别的含义,它仅仅起着占位符的作用。
对象的创建过程
类加载检查
虚拟机在解析.class
文件时,若遇到一条 new 指令,首先它会去检查常量池中是否有这个类的符号引用,并且检查这个符号引用所代表的类是否已被加载、解析和初始化过。如果没有,那么必须先执行相应的类加载过程。
为新生对象分配内存
对象所需内存的大小在类加载完成后便可完全确定,接下来从堆中划分一块对应大小的内存空间给新的对象。分配堆中内存有两种方式:
-
指针碰撞
如果 Java 堆中内存绝对规整(说明采用的是“复制算法”或“标记整理法”),空闲内存和已使用内存中间放着一个指针作为分界点指示器,那么分配内存时只需要把指针向空闲内存挪动一段与对象大小一样的距离,这种分配方式称为“指针碰撞”。 -
空闲列表
如果 Java 堆中内存并不规整,已使用的内存和空闲内存交错(说明采用的是标记-清除法,有碎片),此时没法简单进行指针碰撞, VM 必须维护一个列表,记录其中哪些内存块空闲可用。分配之时从空闲列表中找到一块足够大的内存空间划分给对象实例。这种方式称为“空闲列表”。
初始化
分配完内存后,为对象中的成员变量赋上初始值,设置对象头信息,调用对象的构造函数方法进行初始化。
至此,整个对象的创建过程就完成了。
对象的访问方式
所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在堆栈中分配的。也就是说在建立一个对象时两个地方都分配内存,在堆中分配的内存实际建立这个对象,而在堆栈中分配的内存只是一个指向这个堆对象的指针(引用)而已。 那么根据引用存放的地址类型的不同,对象有不同的访问方式。
句柄访问方式
堆中需要有一块叫做“句柄池”的内存空间,句柄中包含了对象实例数据与类型数据各自的具体地址信息。
引用类型的变量存放的是该对象的句柄地址(reference)。访问对象时,首先需要通过引用类型的变量找到该对象的句柄,然后根据句柄中对象的地址找到对象。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d0Bfdizs-1620090480820)(./images/handle-access.jpg)]
直接指针访问方式
引用类型的变量直接存放对象的地址,从而不需要句柄池,通过引用能够直接访问对象。但对象所在的内存空间需要额外的策略存储对象所属的类信息的地址。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YbdF8UU6-1620090480824)(./images/direct-pointer.jpg)]
需要说明的是,HotSpot 采用第二种方式,即直接指针方式来访问对象,只需要一次寻址操作,所以在性能上比句柄访问方式快一倍。但像上面所说,它需要额外的策略来存储对象在方法区中类信息的地址。