JUC
JUC Java 对象内存布局和对象头

面试题

  • 说下JUC,AQS的大致流程
  • CAS自旋锁,是获取不到锁就一直自旋吗?CAS和synchronized区别在哪里,为什么CAS好,具体优势在哪里?
  • sychronized底层是如何实现的,实现同步的时候用到了CAS 了吗?具体哪里用到了?
  • 对象头存储那些信息?长度是多少位存储?

对象在堆内存中布局

定义

在HotSpot虚拟机里,对象在堆内存的存储布局可以划分为三个部分:对象头(Header)、实例数据(Instance Data) 和对齐填充(Padding)。

对象在堆内存中的存储布局

对象内部结构

  • 对象内部结构分为:对象头、实例数据、对齐填充(保证8个字节的倍数)。
  • 对象头分为对象标记(markOop)和类元信息(klassOop),类元信息存储的是指向该对象类元数据(klass)的首地址。

对象头

对象标记 Mark Word

对象内部结构2

HotSpot 虚拟机对象头 Mark Word

存储内容标志位状态
对象哈希码,对象分代年龄01未锁定
指向锁记录的指针00轻量级锁定
指向重量级锁的指针10膨胀(重量级锁定)
空,不需要记录信息11GC 标记
偏向线程 ID、偏向时间戳、对象分代年龄01可偏向
在 64 位系统中,Mark Word 占了 8 个字节,类型指针占了 8 个字节,一共是 16 个字节

在 64 位虚拟机下,Mark Word 是 64bit 大小的,其存储结构如下

Mark Word 的存储结构

默认存储对象的HashCode、分代年龄和锁标志位等信息。 这些信息都是与对象自身定义无关的数据,所以MarkWord被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。 它会根据对象的状态复用自己的存储空间,也就是说在运行期间MarkWord里存储的数据会随着锁标志位的变化而变化。

类元信息(又叫类型指针)

类型指针

对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

对象头大小

在 64 位系统中,Mark Word 占了 8 个字节,类型指针占了 8 个字节,一共是 16 个字节。

实例数据

存放类的属性(Field)数据信息,包括父类的属性信息, 如果是数组的实例部分还包括数组的长度,这部分内存按4字节对齐。

对齐填充

虚拟机要求对象起始地址必须是8字节的整数倍。 填充数据不是必须存在的,仅仅是为了字节对齐 这部分内存按8字节补充对齐。

官网理论

  • Hotspot术语表官网 https://openjdk.org/groups/hotspot/docs/HotSpotGlossary.html (opens in a new tab)

    klass pointer

    The second word of every object header. Points to another object (a metaobject) which describes the layout and behavior of the original object. For Java objects, the "klass" contains a C++ style "vtable".

    mark word

    The first word of every object header. Usually a set of bitfields including synchronization state and identity hash code. May also be a pointer (with characteristic low bit encoding) to synchronization related information. During GC, may contain GC state bits.

    object header

    Common structure at the beginning of every GC-managed heap object. (Every oop points to an object header.) Includes fundamental information about the heap object's layout, type, GC state, synchronization state, and identity hash code. Consists of two words. In arrays it is immediately followed by a length field. Note that both Java objects and VM-internal objects have a common object header format.

  • 底层源码理论证明 http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/89fb452b3688/src/share/vm/oops/oop.hpp (opens in a new tab)

    oop.hpp
    class oopDesc {
        friend class VMStructs;
        private:
            volatile markOop _mark;
            union _metadata {
                Klass* _klass;
                narrowKlass _compressed_klass;
            } _metadata;
            static BarrierSet* _bs;
        public:
            markOop mark() const {return _mark;}
            markOop* mark_addr() const {return (markOop*) &mark_addr;}
    }

    _mark字段是 mark word_metadata 是类指针 klass pointer,对象头(object header)即是由这两个字段组成,这些术语可以参考Hotspot术语表,

再说对象头的 MarkWord

32 位

32-位虚拟机

64 位

64-位虚拟机

markword(64位)分布图, 对象布局、GC回收和后面的锁升级就是 对象标记MarkWord里面标志位的变化

Hotspot-的实现

聊聊 Object obj = new Object()

JOL 证明

<!--
官网:http://openjdk.java.net/projects/code-tools/jol/
定位:分析对象在JVM的大小和分布
-->
<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.9</version>
</dependency>
JOLDemo.java
public class JOLDemo {
    public static void main(String[] args) {
        // VM 的细节详细情况
        System.out.println(VM.current().details());
        // 所有的对象分配的字节都是 8 的整数倍
        System.out.println(VM.current().objectAlignment());
    }
}
Terminal
# Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-bit shift.
# WARNING | Compressed references base/shifts are guessed by the experiment!
# WARNING | Therefore, computed addresses are just guesses, and ARE NOT RELIABLE.
# WARNING | Make sure to attach Serviceability Agent to get the reliable addresses.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

8

代码示例

JOLDemo.java
public class JOLDemo {
    public static void main(String[] args) {
        Object o = new Object(); //16 bytes
        System.out.println(ClassLayout.parseInstance(o).toPrintable());
    }
}
Terminal
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           68 0f 00 00 (01101000 00001111 00000000 00000000) (3944)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

前两个 object headerMark Word 8 字节,第3个 object header 是类型指针 4 字节

参数描述
OFFSET偏移量,也就是到这个字段位置所占用的byte数
SIZE后面类型的字节大小
TYPE是Class中定义的类型
DESCRIPTIONDESCRIPTION是类型的描述
VALUEVALUE是TYPE在内存中的值

MaxTenuringThreshold

GC年龄采用4位bit存储,最大为15, 例如MaxTenuringThreshold参数默认值就是15

-XX:MaxTenuringThreshold=16

Terminal
MaxTenuringThreshold of 16 is invalid; must be between 0 and 15
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.

类型指针压缩

Terminal
leam@Leam-MBP juc % java -XX:+PrintCommandLineFlags -version
-XX:ConcGCThreads=2 -XX:G1ConcRefinementThreads=9 -XX:GCDrainStackTargetSize=64 -XX:InitialHeapSize=536870912 -XX:MarkStackSize=4194304 -XX:MaxHeapSize=8589934592 -XX:MinHeapSize=6815736 -XX:+PrintCommandLineFlags -XX:ReservedCodeCacheSize=251658240 -XX:+SegmentedCodeCache -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseG1GC 
openjdk version "17.0.9" 2023-10-17 LTS
OpenJDK Runtime Environment Zulu17.46+19-CA (build 17.0.9+8-LTS)
OpenJDK 64-Bit Server VM Zulu17.46+19-CA (build 17.0.9+8-LTS, mixed mode, sharing)
  • -XX:+UseCompressedClassPointers

    Terminal
    java.lang.Object object internals:
     OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
          0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
          4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
          8     4        (object header)                           68 0f 00 00 (01101000 00001111 00000000 00000000) (3944)
         12     4        (loss due to the next object alignment)
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
  • -XX:-UseCompressedClassPointers

    Terminal
    java.lang.Object object internals:
     OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
          0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
          4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
          8     4        (object header)                           b0 1c de 20 (10110000 00011100 11011110 00100000) (551427248)
         12     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

换成其他对象试试

JOLDemo
public class JOLDemo {
    public static void main(String[] args) {
        Customer c = new Customer(); //16 bytes
        System.out.println(ClassLayout.parseInstance(c).toPrintable());
    }
}
 
// 只有一个对象头的实例对象,16字节(忽略压缩指针的影响)+ 4字节 + 1字节=21字节 -》 对其填充,24字节
class Customer {
    // 1 第一种情况,只有对象头,没有其它任何实例数据
 
    // 2 第二种情况,int + boolean,默认满足对其填充,24 bytes
//    int id;
//    boolean flag = false;
//    boolean flag2 = false;
}
 
/**
 * 1 默认配置,启动了压缩指针 -XX:+UseCompressedClassPointers,
 *   12 + 4(对齐填充) = 一个对象16字节
 *
 * 2 手动配置,关闭了压缩指针 -XX:-UseCompressedClassPointers,
 *   8 + 8 = 一个对象16字节
 */