对象的创建

1. Java 程序员视角 vs. JVM 内部视角

在 Java 语言层面,创建对象往往就是 new 关键字加上构造方法,看似很简单。但在 JVM 内部,当遇到一条字节码指令 new 时,需要经过以下步骤:

  1. 检查类是否已经被加载、解析和初始化

    • 如果没有,需要先完成类的加载、解析和初始化(这是类加载机制的工作,可以看我写的类加载内篇笔记)。

  2. 为新生对象分配内存

    • 虚拟机会从 Java 堆中为对象分配一块连续且足够的空间。

    • 分配方式取决于堆是否规整,常见的有「指针碰撞」和「空闲列表」两种:

      1. 指针碰撞 (Bump the Pointer)

        • 若堆中空闲内存是连续的,一边放已使用的内存,一边放空闲的内存,中间是一个分界指针。

        • 分配时,只需把指针向空闲方向移动一段对象大小的距离。

        • 优点:操作简单,效率高。

        • 适用场景:当垃圾收集器具有「压缩整理」功能(如 Serial、ParNew)时,堆会保持规整。

      2. 空闲列表 (Free List)

        • 若堆中的空闲内存和已用内存交织在一起,就无法用指针碰撞了。

        • 需要维护一个「空闲列表」,找到一块足够大的内存给对象。

        • 适用场景:当垃圾收集器采用「清除 (Sweep)」算法(如 CMS)时,堆可能不规整。

  3. 解决并发下的线程安全问题

    • 对象创建在多线程下是非常频繁的操作,如果多个线程同时为对象分配内存会产生冲突。

    • 两种方案

      1. CAS + 失败重试:用原子操作保证指针移动的正确性;

      2. TLAB (Thread Local Allocation Buffer):为每个线程预先在堆中分配一小块缓冲区(TLAB),这样每个线程只在自己的 TLAB 中分配对象,只有当 TLAB 用完后才需要同步锁定分配新的缓冲区。

    • 是否开启 TLAB 可通过 -XX:+/-UseTLAB 参数设置。

  4. 将分配到的内存初始化为零值

    • 对象的实例字段会在 Java 层面默认使用「零值」(int=0, boolean=false, 对象引用=null 等)。

    • 为了保证这一点,在分配时会把分配到的内存(除对象头外)清零。

  5. 设置对象头信息

    • JVM 需要为对象设置「它是哪个类的实例、如何找到类元数据、对象的哈希码、GC 分代年龄、锁标志位」等信息,这些都放在对象头(Object Header)中。

    • 若启用偏向锁(Biased Locking),对象头会带有相应标记。

  6. 执行 <init>() 构造方法

    • 上面步骤完成后,从 JVM 角度看,对象已经生成了,但对于 Java 程序来说,构造方法还没执行,字段值只是默认值。

    • 一般情况下(new 指令后跟 invokespecial 来调用构造方法),Java 程序会调用 <init>(),按照程序员的逻辑进行初始化。这样对象才算真正完成构造、可被使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值