大厂面试都爱问的---对象实例化、内存布局

大厂面试都爱问的—对象实例化、内存布局

在我们编写Java代码时,最常做的就是创建对象.那么你是否有知道创建对象的一个具体实现呢?

今天我们就来说一说 创建对象的一个具体实现.

一 对象实例化

1 创建对象的几种方式:

①new —>也是最常用的一种方式,直接通过 需要创建对象的类的构造器来进行创建.

他引申出了两种变形方式 :

1️⃣一个类没有对外声明构造器,但是我们可以通过类对外声明的静态方法来获取一个这个类的实例.

2️⃣ 通过xxxFactory 工厂类来获取对应的类的实例.

②Class的newInstance()方法.

通过反射来获取一个对应类的实例对象,但是这个方法在JDK1.9之后就被标记为过时了,因为这个方法有很大的局限性,我们只能通过访问对应类的无参构造器,而且访问权限必须是public 才能进行创建实例.

③Constructor的newInstance()方法.

通过Constructor对应的类的构造器方法来获取实例,这个方法不再被 构造器的参数列表及访问权限所限制,可以自由地创建我们需要的实例.

④clone()方法.

通过clone()方法,获取一个实现过Cloneable接口且有实例对象的 类的另外一个实例,有深克隆和浅克隆两种方式,根本区别就是 需要克隆的实例的属性如果是引用数据类型时,是否也实现了Cloneable接口.

⑤使用反序列化

序列化与反序列化就是Java数据传输的机制,要求序列化的类必须实现Serializable接口.我们在反序列化时,使用new ObjectInputStream(new FileInputStream(XXXX)) 来读取字节文件,反序列还原出我们需要的Java实例

⑥使用一些第三方库来绕过构造器创建实例

比如 Objenesis 这个库,是一个轻量级的三方库,我们可以简单的通过实现Objenesis下的两个接口,然后将我们需要实例化的类的.class传入其内部的方法来创建实例.

2 对象实例化的步骤

①判断对象对应的类是否已经被加载.

通过去元空间(1.8以前叫方法区)查找是否有对应类的类元信息是否存在.如果未加载的话,通过双亲委派机制,调用对应的ClassLoader来加载所需要的类的字节码文件.

②为对象在堆空间中分配内存.

因为类的信息已经被加载了,所以对象所需要的内存大小就可以确定,我们就可以根据内存大小为其及其父类在堆空间中分配相应的内存区域.

因为堆空间是线程共享区域,所以必不可少的就存在多线程安全问题,这个时候就必须解决其线程安全问题.

主要是有两种解决办法:

1️⃣使用CAS进行区域加锁,在我们实例化对象的过程中,会比较这个经过我们操作之后,读取原来数据得到的值N是否等于我们未操作之前的值E,只有想等的话,才会改变这个值把E更新为V,如果不相等,就会一直在这里自旋,直到N等于E 的时候,把E更新到V 才会退出自旋.如果在我们创建对象的时候,其他线程进来操作了我们本来分配的空间,导致 这个空间的值不等于我们期望的创建的对象的值,那么就会在这里自旋,进程就会阻塞.

CAS原理图:

在这里插入图片描述

2️⃣因为堆空间是多线程共享的,JVM为了防止出现过多的线程不安全,在堆空间又给每一个线程单独开辟出了一个区域,叫做TLAB, TLAB是线程私有的,大小为堆空间大小的1%,如果线程要开辟的内存空间大小不大于TLAB的空间大小的话,就会在此区域开辟,从而避免了线程不安全性.

③初始化分配到的内存空间.

也就是为实例化的对象进行默认初始化赋值.

④配置对象的对象头的信息.

对象头内包括运行时元数据和类型指针.如果是数组的话,还会记录数组的长度.

⑤执行init方法.

调用类的相应的构造器方法进行构造器赋值

二 内存布局

先给大家来张图,后面详细讲:

在这里插入图片描述

1 对象头

1.1运行时元数据(Mark Word):

①哈希值

②GC分代年龄值

③锁状态标志

④线程持有的锁

 ⑤偏向锁ID

⑥偏向时间戳

Mark Word是储存运行时的数据, 对应的大小 32位的JVM是32bits,64位的JVM是64bits.

因为我是64位的JVM ,所以mark word是 8bytes.

在这里插入图片描述

图中详细写出了 各种锁状态下 各数据所占的bits.

1.2类型指针(Class Pointer):

类型指针是指向该对象所属的类型.明确对象类型.

在32位JVM当中,Class Pointer占4个bytes, 在64位JVM中,如果不开启压缩的话是8个bytes,但是JVM默认开启压缩,占4个bytes.

所以整个对象头是 12个bytes.

2 实例数据:

实例数据中存放的事 对象内存布局中真正存储的有效信息. 包括程序代码中的定义的各种类型的字段(包括从父类继承下来的).

实例数据存储有三条规则:① 相同宽度的字段总是分配在一起

				    ② 父类定义的变量出现在子类之前.

				    ③ 如果compactFields参数开启,子类中的变量可能插入到父类变量的空隙中.

实例数据的所在的字节数根据具体定义的字段大小来确定.

(char,byte,short,int,float,boolean 占用4个字节, long,float占用8个字节, 引用数据类型占用4个字节.)

3 对齐填充:

因为不同的实例对象得到的上述所有的信息加在一起的长度是不同的(实例数据个数及类型不确定).为了数据整齐及计算效率, 将 对象的内存布局长度 补齐至8的倍数(因为8的倍数计算效率最高)

以上…

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值