目录
一、结构内容
二、Java 对象实例化过程
1. 大厂面试问题
- Java 对象在 JVM 中如何存储?
- Java 对象头中保存哪些内容?
2. Java 对象创建的方式(5 种)
new
:及其各种变种,包括单例模式
和构建模式
中调用静态方法(其实间接调用了私有构造方法),这是最常见的方式。- 反射:
- Class 类 newInstance 方法, 要求空参构造函数、public。 JDK 9 中已标记为过时方法。
- Constructor 类的 newInstance 方法,可以调用带参构造器。也不要求 public。
clone
:用于实现了 Clonable 的类,不调用任何构造器- 反序列化:使用来自文件或网络的字节信息重新构建类
- 第三方框架:如 Objenesis 等
3. Java 对象的创建过程(6 步骤)
类型信息
加载
虚拟机遇到new
指令,会去 MetaSpace 常量池中定位类符号引用。如果没有找到会通过类加载机制对类型进行加载,并生成相应的 Class 对象。- 为对象
分配内存
因为采用的 GC 机制(压缩整理功能)不同,会导致堆内存规整情况不同。因此有两种内存分配机制:
-
指针碰撞(Bump the Pointer),用于内存规整的情况。内存分为已使用区和空闲区,用一个指针指示两个分区的分界。给对象分配内存时,只需将指针向空闲区移动对象大小即可。
-
空闲列表(Free List)
已使用和空闲内存相互交错,虚拟机维护了一个列表,记录了哪些内存块时可用的,在分配的时候从列表中找出一块足够大的空间划分给对象实例。并更新列表内容。
- 处理
并发安全
问题
采用 CAS 机制尝试分配,同时也采用 TLAB 预先为线程分配小块堆空间 - 设置
实例字段
默认值
所有实例字段分配默认值,保证在没有赋值时可以直接使用 - 设置对象的
对象头
信息
对象头中会存放对象运行时元数据和类型指针。详见下一节。 - 执行
init
方法进行初始化
显式初始化成员变量,执行代码块和执行构造方法。此时才按程序员的意愿进行初始化。至此,一个对象才完整的创建出来。
三、Java 对象的内存布局
1. 一个示例
public class CustomerTest{
public static void main(String[] args) {
Customer customer = new Customer();
}
}
class Customer {
int id = 1001;
String name;
Account account;
{
name="匿名客户";
}
public Customer() {
account = new Account();
}
}
class Account {
}
说明
- 主线程的虚拟机栈中有一个 main 方法对应的栈帧, 栈帧中的局部变量表有一个 customer 引用类型指向 堆中的 Customer 对象
- Customer 对象中内存划分三个区域:对象头、实例数据和对齐填充
- 对象头中的类型指针指向方法区中 Customer 类元信息
2. 对象内存划分详细说明
2.1 对象头
2.2 实例数据
2.3 对齐填充
非必须,仅仅起到占位符的作用,类比于快递盒中的泡沫,用于让堆内存更规整。
四、Java 对象的访问
Java 对象是通过虚拟机栈上的引用变量来实现访问的。
1. 句柄访问
堆区划分成句柄池和实例池,栈上引用指向句柄池中的句柄。每个句柄中包括:
- 指向对象实例数据的指针:指向对象池中的对象
- 执行对象类型数据的指针:指向方法区中的对象类型
优点
在垃圾回收等过程中对象发生移动时,只需要更改句柄而不需要堆栈上的引用进行修改,比较稳定。
2. 直接指针
栈上的引用变量直接执行堆中的对象实例,对象实例中包含了一个指向对象类型数据的指针。
优点
- 无需额外保存句柄信息,节省内存空间
- 比句柄方式少了一次访问操作,因此会更高效
附件
本文没有 PPT