Bootstrap

【深入理解JVM】关于Object o = new Object()

1. 解释一下对象的创建过程

“半初始化”状态通常指的是对象在内存分配后、但在完全初始化之前的一种状态。在Java中,虽然JVM的规范和设计努力避免对象处于这种不稳定的状态,但在多线程环境下,由于指令重排序等并发问题,仍有可能出现对象半初始化的现象。

具体来说,如果JVM在分配了对象的内存空间后,尚未完成构造方法的执行(即对象还未完全初始化),而另一个线程已经获得了这个对象的引用,那么这个对象就处于“半初始化”状态。此时,如果尝试访问对象的属性或方法,可能会得到不一致或错误的结果。

2. DCL要不要加volatile

在Java中,当涉及到双重检查锁定(Double-Checked Locking, DCL)模式时,确实需要特别考虑线程安全问题,尤其是对象的延迟初始化。DCL模式允许你在多线程环境下延迟对象的初始化,同时减少同步的开销。然而,如果不正确使用,它可能导致所谓的“半初始化”问题,即一个线程可能看到对象的引用但尚未看到其完全初始化的状态。

为了避免这个问题,在Java中,当使用DCL模式时,确实需要将涉及的变量声明为volatile。这是因为volatile关键字有两个主要特性:

可见性:确保一个线程对变量的修改对其他线程是立即可见的。这防止了线程在读取变量时可能看到的过时值(即缓存中的值)。
禁止指令重排序:在多线程环境中,编译器和处理器可能会对指令进行重排序以优化性能。然而,这种重排序可能会破坏程序的语义。volatile关键字可以禁止这种重排序,特别是在涉及变量的读写操作时。

3. 对象在内存中的存储布局

存储布局:在Java堆中,对象按照其生命周期被划分为不同的代(如年轻代、老年代等),但这主要影响垃圾收集的策略,而非对象本身的存储布局。从对象结构的角度看,一个对象在堆中的存储通常包括以下几个部分:

  • 对象头(Object Header):包含对象的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。此外,如果对象是一个数组的实例,那么对象头中还需要记录数组的长度。(对象头又分为markword和classpointer
  • 实例数据(Instance Data):存放类的属性信息,包括从父类继承的属性。这部分内存的分配受到JVM自动内存管理系统管理。
  • 对齐填充(Padding):由于JVM要求对象起始地址必须是8的倍数,因此对象占用的内存空间必须是8的倍数。如果对象实例数据部分没有对齐,就需要通过对齐填充来补全。
    在这里插入图片描述

4. 对象头具体包括什么?(Object Header)

  • markword:锁信息、哈希码(HashCode)、GC回收信息
  • classpointer:指向new出来对象的类

对象头(Object Header):包含对象的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。此外,如果对象是一个数组的实例,那么对象头中还需要记录数组的长度。

5.对象怎么定位?

在这里插入图片描述
对象定位主要有两种方式:

  • 句柄方式:
    这种方式使用两个指针来定位对象。第一个指针指向实际new出来的对象,第二个指针是指向类型数据的指针。这种方式在垃圾回收时相对稳定,因为即使对象在内存中移动,句柄也不会改变,只需更新句柄中指向对象的指针即可。
    句柄方式的一个优点是垃圾回收的稳定性和效率,尤其是在使用如G1这样的垃圾回收器时,它可以有效处理对象移动的情况。
  • 直接指针方式:
    直接指针方式通过直接存储对象的内存地址来访问对象,这种方式访问速度通常较快。然而,在垃圾回收时,如果对象被移动,直接指针需要更新,这可能会导致效率问题。
    因此,在需要频繁垃圾回收的场景下,直接指针方式可能不如句柄方式稳定。

6.对象怎么分配?

在Java中,对象主要在堆(Heap)内存中分配空间。
堆内存是Java虚拟机(JVM)所管理的内存中最大的一块,用于存放由new创建的对象和数组。堆内存还可以细分为新生代(Young Generation)、老年代(Old Generation)等区域
在这里插入图片描述

7. Object o = new Object()在内存中占用多少字节?

16个字节(8个字节markword,4个字节class pointer、0个字节成员变量,4个字节对齐)(对齐字节数需要被8整除)

;