JVM对象的实例化

对象实例化步骤

  1. 判断对应的类是否已加载
  • (1) 当 new一个对象时, 首先检查 new指令的参数(如 new Demo()的类)能否在常量池中定位到一个类的符号引用(即判断类元信息是否存在)
  • (2) 如果没有, 则按双亲委派机制, 使用当前类加载器加载对应的 .class文件, 并生成对应的 Class类对象. 此时如果找不到对应的 .class文件, 则抛出ClassNotFoundException异常
  1. 为对象分配内存
  • 首先计算对象将要占用的空间大小, 然后在堆中分配内存给新对象
    * 对象是按成员属性类型和个数来分配空间的(如 当成员属性是引用类型, 则占用32位, 也就是分配4个字节)

  • 内存分配有两种方式:

  • (1) 指针碰撞(Bump The Pointer): 当一个内存区域的垃圾收集器, 所采用的清楚阶段算法是标记压缩(Mark-Compact, 标记整理)算法, 此区域的内存是属规整的, 因为每当垃圾回收时, 都会将标记过的存活对象依次整理, 将其可用的内存地址具有连续性, 以此解决了内存碎片问题. 所谓整理的过程就是指针移动的过程, 又称指针碰撞

  • 支持标记压缩算法的回收器. 如 串型回收器(Serial), 并行回收器(ParNew)等
    在这里插入图片描述

  • (2) 空闲列表(Free List): 当一个内存区域的垃圾收集器, 所采用的清楚阶段算法是标记清楚(Mark-Sweep)算法, 此区域的内存是属不规整的, 因为每当回收未被标记的对象后内存地址不做整理(此处不同于标记压缩算法), 由此导致可用的内存地址不连续, 因此产生了很多内存碎片. 所以需要额外维护一个空闲列表来记录这些可用的内存地址, 以备分配新的对象时使用

  • 支持标记清楚算法的回收器. 如 并发回收器(CMS)
    在这里插入图片描述

  • 也就是选择哪种分配方式是由所采用的垃圾收集器决定
  1. 处理并发安全问题
  • 由于堆空间是线程共享的区域, 所以有线程安全问题. 因此每当创建对象时会先尝试分配 TLAB缓冲区(Jdk8开始默认开启), 如果未分配到 TLAB, 就会采用 CAS(Compare and Swap, 比较再交换: 一种[乐观锁/无锁算法])失败重试机制, 加锁来保证数据的原子性
  1. 成员属性初始化
  • 按属性类型给每个属性赋予默认值
  1. 设置对象头
  • 将对象的所属类(即类的元信息), 对象的 HashCode, GC信息, 锁信息等数据存储在对象头中
  1. 执行 init方法进行初始化
  • (1) 显式的初始化和代码块中的初始化, 这两种赋值顺序是与 Java代码顺序相关
  • (2) 通过构造器初始化

对象的内存布局

  1. 对象头(Object Header)
  • (1) 运行时元数据(Mark Word):
  • 哈希值(HashCode)
  • GC分代年龄
  • 锁状态标志
  • 线程持有的锁
  • 偏向线程ID
  • 偏向时间戳
  • (2) 类型指针:
  • 即对象指向的类的元数据指针(InstanceKlass), 虚拟机通过这个指针确定这个对象属哪个类的实例. (可以通过Object的 getClass方法能知道这个对象是通过哪个类所创建的, 但并不是所有的对象都会保留这个类型指针)
    * 如果是数组, 还需记录数组的长度(数组也是对象)
  1. 实例数据(Instance Data)
  • 程序代码中定义的各种类型的字段(包括从父类继承下来的和本身拥有的字段, 父类中定义的变量总会在子类之前)
  1. 对齐填充(Padding)
  • 不是必须的, 也没特别含义, 仅仅起到占位符的作用

对象内存布局代码实例


/** 对象属性赋值过程:
 * 1. 属性默认值初始化
 * 2. 显式的初始化/代码块中初始化
 * 3. 构造器中初始化
 * */
public class Member {
    int id; # 属性默认值初始化
    int age = 35; # 显式的初始化
    String name;
    {
        name = "全大爷"; # 代码块中初始化
    }

    Wallet wal;

    public Member() {
        wal = new Wallet(); # 构造器中初始化
    }
}

class Wallet {}

public class UserApp {
    public static void main(String[] args) {
        Member mem = new Member();
    }
}

  • 对象内存布局图示
    在这里插入图片描述

对象的访问定位

  • 使用对象时, 通过栈上的 reference数据来操作堆上的具体对象, 有两种方式:
  • (1) 句柄访问对象

图 句柄访问对象.png
在这里插入图片描述

  • (2) 直接指针访问对象(Hotspot虚拟机采用的方式)
    在这里插入图片描述
    * 句柄访问对象相对直接指针访问对象优点为, 当对象被移动时, 无需改动栈上的 reference, 而改动堆上的指定句柄里的对象实例数据指针即可, 但需要额外维护句柄池. 所以性能上总的来讲直接指针访问对象更优一些

如果您觉得有帮助,欢迎点赞哦 ~ 谢谢!!

猜你喜欢

转载自blog.csdn.net/qcl108/article/details/108685799