0035-对象实例化步骤,内存布局,访问定位

文章目录

      • 1. 实例化步骤
      • 2. 内存布局
      • 3. 对象访问定位

1. 实例化步骤

1. 对象创建的方式

  1. new

  2. Class的newInstance()

  3. Contructor的newInstance(xxx)

  4. 使用clone

  5. 使用反序列化

  6. 第三方库Objenesis

2. 对象的创建步骤

  1. 判断对象对应的类是否加载,链接,初始化,没有加载,使用双亲委派机制加载类

  2. 为对象分配内存
    不同的垃圾收集器会导致,内存规整或不规整,不同的内存结构,会有不同的分配方式
    2.1 内存规整-指针碰撞
    2.2 内存不规整-维护空闲列表

  3. 处理并发安全问题,内存分配必须是线程安全的,同一块堆内存,同一时间只能由一个线程使用
    3.1 采用CAS保证原子性
    3.2 分配TLAB

  4. 初始化分配到的空间-零值初始化,为所有属性设置默认值, 保证对象实例字段不赋值可以直接使用

  5. 设置对象的对象头

  6. 执行init方法进行初始化(构造方法)

2. 内存布局

在java堆内存上的对象由三部分组成

  1. 对象头

  2. 实例数据

  3. 对齐填充

1. 对象头
包含两部分数据,如果是数组,还要记录数组的长度

  1. 运行时元数据(Mark Word)
    1.1 哈希值(HashCode)
    1.2 GC分代年龄
    1.3 锁状态标志
    1.4 线程持有的锁
    1.5 偏向线程ID
    1.6 偏向时间戳

  2. 类型指针
    指向类元数据InstanceKlass,确定该对象所属的类型

2. 实例数据
对象真正存储有效信息的地方,包括程序代码中定义的各种类型的字段,包括从父类继承下来的和本身拥有的字段

存储规则

  1. 相同宽度的字段总是被分配到一起

  2. 父类中定义的变量会出现在子类之前

  3. 如果CompactFields参数为true,子类的窄变量可能插入到父类变量的空隙

3. 对齐填充
没有特殊含义,仅仅起到占位符的作用

4. 实例

public class Customer{
    int id = 1001;
    String name;
    Account acct;

    {
        name = "匿名客户";
    }
    public Customer(){
        acct = new Account();
    }

}
class Account{

}

上述代码的内存布局如下图所示


0035-对象实例化步骤,内存布局,访问定位_第1张图片

  1. main方法的栈帧被压入栈,栈帧中的局部变量有args和cust两个变量

  2. cust指向堆空间的Custtomer实例

  3. Customer实例包含三部分信息:对象头,实例数据和对齐填充

  4. 对象头重的类型信息指向方法区的类元信息

  5. 实例数据的字符串指向字符串常量池

3. 对象访问定位

jvm如何通过栈帧中的对象引用访问到其内部的对象实例
0035-对象实例化步骤,内存布局,访问定位_第2张图片

访问方式

  • 句柄访问


    0035-对象实例化步骤,内存布局,访问定位_第3张图片
  1. java堆区中额外维护一份句柄信息,指向堆中对象和对象类型

  2. 虚拟机栈中的局部变量表保存句柄信息

  • 直接指针(HotSpot采用)


    0035-对象实例化步骤,内存布局,访问定位_第4张图片

栈中局部变量直接保存对象指针信息

两种访问方式比较

  1. 直接指针方式,不维护句柄信息,节省空间,访问更快

  2. 使用句柄池方式,栈中局部变量保存的地址始终不用变动
    堆中对象移动时,直接地址方式保存地址需要改变地址,句柄方式则不用

你可能感兴趣的:(#,Java虚拟机,jvm)