JVM - 运行时数据区 - 对象的创建和布局

1. 对象的创建

JVM虚拟机把.class文件中类信息加载进内存,并进行解析生成对应的class对象

虚拟机在遇到new指令时,首先回去检查要实例化的这个类是否被加载,解析,初始化过,之后开始对象的创建

1.1 对象创建的方式

1. new 
2. Class的newInstance()反射创建,只能调用空参的构造器
3. Constructor的newInstance()反射方法,可以调用带参的构造器
4. clone()方法
5. 使用反序列化
6. 第三方类库Objenesis

1.2 对象创建的步骤

① 判断对象对应的类是否加载,链接,初始化
虚拟机遇到一条new指令,首先去检查这个指令的参数能否在Metaspace的常量池中定位到一个类的符号引用,并且检查这个符号引用所代表的类是否被加载,解析,初始化(即判断类元信息是否存在);
如果没有,就在双亲委派的机制下,使用类加载器以ClassLoader+包名+类名为key找对应的.class文件,找到了就进行加载并生成对应的Class类对象;找不到就抛出ClassNotFoundException异常

② 为对象分配内存
在对象创建的第一步是为将要新生成的对象分配内存,这个所需的内存在类加载完成后就能确定下来;

分配内存有两种策略,他们针对堆的内存是否规整而使用
① 指针碰撞:假如堆中的内存是规整的,使用过的内存放在一边,没有使用过的内存放在另一边,中间放一个指针作为分隔,当要分配内存的时候,将分割指针向没有使用的内存的那边对象大小的距离即可;

② 空闲列表:如果堆中的内存不是规整的,那么需要维护一个列表来记录哪些内存被使用过哪些没有使用从而分配

对于堆的内存是否规整则使用由垃圾收集器时候由压缩整理功能决定的

③ 处理并发安全问题
除了考虑如何分配内存以外还需要考虑分配内存时的线程安全问题,防止两个线程同时修改一块内存区域,JVM有两种方案
CAS+失败重试,区域加锁:保证指针更新操作的原子性
② 把内存分配的动作按照线程划分在不同的空间进行(TLAB

④ 初始化分配的空间 - 属性的零值初始化
对应为什么成员变量可以不赋初值访问就是因为这一步执行的结果
内存分配完成以后,虚拟机需要将分配到的内存空间都初始化为零值,这一步操作保证了实例字段不赋初值也可以使用

⑤ 设置对象的对象头
将对象的所属类(类的元数据信息),对象的HashCode和对象的GC信息,锁信息等数据存储到对象的对象头之中

⑥ 执行<init>方法进行初始化 - 属性的显示初始化,语句块初始化,构造器初始化
关于 <init><cinit>
在上面的工作完成以后,从JVM的层面看,一个新的对象就已经产生了,但是从Java程序的视角看,对象创建才刚刚开始,因为还要执行构造方法等;

Java 在编译之后会在字节码文件中生成 init 方法,称之为实例构造器,该实例构造器会将语句块,变量初始化,调用父类的构造器等操作收敛到init这个方法中,收敛顺序为(这里只讨论非静态变量和语句块)为:

1.父类变量初始化 --> 2.父类语句块 --> 3.父类构造函数 --> 4.子类变量初始化 --> 5.子类语句块 --> 6.子类构造方法

注意不要和<cinit>方法搞混淆

Java在编译之后会在字节码文件中生成clinit方法,称之为类构造器。类构造器同实例构造器一样,也会将静态语句块,静态变量初始化,收敛clinit方法中。收敛顺序为:

1.父类静态变量初始化 --> 2.父类静态语句块 --> 3.子类静态变量初始化 --> 4.子类静态语句块

若父类为接口,则不会调用父类的clinic方法,一个类可以没有Clinit方法。
clinit 是在类加载过程中执行的,而 init 方法是在对象实例化中进行的,所以Clinit一定比 init 先执行。

2. 对象的内存布局

HotSpot虚拟机中,对象在内存中的存储的布局可以分为3块区域:对象头(Header),实例数据(Instance Data)和对齐填充(Padding

Hotspot的对象头主要包括两部分数据:Mark Word(标记字段)、Klass Pointer(类型指针)

Mark Word: 默认存储对象的HashCode,分代年龄和锁标志位信息。Mark Word用于存储对象自身的运行时数据,所以Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。它会根据对象的状态复用自己的存储空间,也就是说在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化。

Klass Point: 对象指向方法区中它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例(getClass()方法就可以得到Class对象)

以 32 位虚拟机为例
① 普通对象
在这里插入图片描述
其中的Klass Word结构
类型指针是对象指向它的类元数据的指针,虚拟机通过这个指针确定这个对象属于哪个类的实例

其中的Mark Word结构
在这里插入图片描述

从上图中可以看出,对象的状态一共有五种,分别是无锁态轻量级锁重量级锁GC标记偏向锁。在32位的虚拟机中有两个Bits是用来存储锁的标记为的,但是我们都知道,两个bits最多只能表示四种状态:00、01、10、11,那么第五种状态如何表示呢 ,就要额外依赖1Bit的空间,使用0和1来区分

在32位的HotSpot虚拟机 中对象未被锁定的状态下,Mark Word的32个Bits空间中的25Bits用于存储对象哈希码(HashCode),4Bits用于存储对象分代年龄,2Bits用于存储锁标志位,1Bit固定为0,表示非偏向锁

实例数据就是程序代码中定义的各种类型的字段

以下面代码为例子,总结以下整体的内存布局

public class Customer{
	int id = 1001;
	String name;
	Account acct;
	{
		name = "minifull";
	}
	public Customer(){
		acct = new Account();
	}
	public static void main(String[] args){
		Customer cust = new Customer();
	}
} 
class Account{
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值