开发者

Java的对象头原理与源码超详细讲解

目录
  • 前言
  • 一、什么是Java对象头?为什么需要它?
    • 1.1 对象头的概念
    • 1.2 为什么需要对象头?
  • 二、Java对象头的结构
    • 2.1 Mark Word
      • 完整位划分
      • 32位JVM中是这么存的:
    • 2.2 Class Metadata Address
      • 2.3 Array Length
      • 三、对象头的作用和底层原理
        • 3.1 对象头在锁机制中的作用
          • 3.2 对象头在垃圾回收中的作用
            • 3.3 对象头在哈希码中的作用
            • 四、对象头的源码分析
              • 4.1 Mark Word的定义
                • 4.2 对象头的内存布局
                  • 4.3 锁状态的切换逻辑
                    • 4.4 Monitor与对象头的交互
                    • 五、对象头的内存开销和优化
                      • 5.1 内存开销
                        • 5.2 指针压缩
                          • 5.3 锁优化
                          • 六、完整流程
                            • 七、通俗总结
                              • 八、扩展阅读

                                前言

                                本文将从底层原理和源代码层面详细解释Java的对象头(Object Header),并且尽量用通俗易懂的语言让初学者也能理解。首先从概念开始,逐步深入到实现细节,涵盖对象头的结构、作用、源码分析,并提供完整的步骤和推导。内容清晰、结构化,避免过于晦涩的技术术语。由于对象头是Java锁机制(如synchronized)的基础,我会适当结合锁的场景来增强理解。

                                一、什么是Java对象头?为什么需要它?

                                1.1 对象头的概念

                                 在Java中,每个对象(比如new Object()创建的对象)在内存中不仅存储了它的实际数据(字段值),还有一个额外的“标签”部分,称为对象头(Object Header)。你可以把对象头想象成一个身份证,记录了对象的身份信息和状态,比如:

                                • 这个对象是否被锁住?
                                • 这个对象属于哪个类?
                                • 这个对象的年龄(用于垃圾回收)?

                                对象头就像一个“管理面板”,JVM(Java虚拟机)通过它来管理对象的生命周期、锁状态和内存分配。

                                1.2 为什么需要对象头?

                                对象头的主要作用是为JVM提供元数编程客栈据(Metadata),支持以下功能:

                                1. 锁机制:实现synchronized锁,记录锁状态(比如哪个线程持有锁)。
                                2. 垃圾回收:记录对象的分代年龄(GC Age),决定对象是否需要被回收。
                                3. 类型信息:指向对象的类信息,确保JVM知道这个对象是哪个类的实例。
                                4. 哈希码:存储对象的hashCode()值,用于HashMap等数据结构。
                                5. 数组支持:如果对象是数组,记录数组长度。

                                没有对象头,JVM就无法高效管理对象,也无法实现多线程的线程安全。

                                二、Java对象头的结构

                                Java对象头的结构在JVM实现中(以HotSpot JVM为主)分为几个部分,主要包括:

                                1. Mark Word:动态变化的部分,存储锁状态、哈希码、分代年龄等。
                                2. Class Metadata Address:指向对象所属类的元数据地址。
                                3. Array Length(可选):如果对象是数组,存储数组长度。

                                以下是对象头在内存中的典型布局(以64位JVM为例,假设未开启指针压缩):

                                部分大小(64位JVM)描述
                                Mark Word8字节(64位)锁状态、哈希码、GC年龄等
                                Class Metadata Address8字节(64位)指向类的元数据地址
                                Array Length4字节(可选)数组长度(仅数组对象有)

                                2.1 Mark Word

                                Mark Word 是对象头中最复杂、最动态的部分。它的内容会根据对象状态(无锁、锁住、GC标记等)变化。Mark Word 通常包含以下信息:

                                • 锁状态:无锁、偏向锁、轻量级锁、重量级锁。
                                • 线程ID:持有锁的线程ID(偏向锁时)。
                                • 哈希码:对象的hashCode()值。
                                • 分代年龄:用于垃圾回收,记录对象经历的GC次数。
                                • 锁记录指针:轻量级锁或重量级锁时,指向锁记录或Monitor。

                                Mark Word 的结构会根据锁状态动态调整。例如,在无锁状态下,它会存储哈希码和GC年龄;在锁住状态下,它会存储线程ID或Monitor指针。

                                以下是 Mark Word 在不同状态下的典型布局(64位JVM,未压缩指针):

                                锁状态63-56bit55-2bit1-0bit(锁标志)
                                无锁GC年龄对象的哈希码01
                                偏向锁线程IDEpoch01
                                轻量级锁指向锁记录的指针00
                                重量级锁指向Monitor的指针10
                                GC标记GC相关信息11

                                完整位划分

                                状态位范围字段名大小(位)说明
                                无锁63-31未使用33保留位,通常为 0,未来可能扩展使用。
                                30-8哈希码 (hash)23对象的 hashCode() 值,调用 System.identityHashCode() 时生成。
                                7-4分代年龄 (age)4垃圾回收年龄,记录对象经历的 Minor GC 次数(最大 15)。
                                3-2未使用2保留位,通常为 0。
                                1-0锁标志位201,表示无锁状态。
                                偏向锁63-56未使用8保留位,通常为 0。
                                55-8线程 ID48持有偏向锁的线程 ID,标识哪个线程“偏向”这个对象。
                                7-6Epoch2偏向锁的时间戳,用于批量撤销偏向锁(优化机制)。
                                5-4分代年龄 (age)4同无锁状态,记录 GC 年龄。
                                3偏向锁标志11,表示是偏向锁(与无锁区分)。
                                2-1未使用2保留位,通常为 0。
                                0锁标志位11,与偏向锁标志一起组成 01(最低 2 位)。
                                轻量级锁63-2锁记录指针62指向线程栈中的锁记录(Displaced Headerjs),保存原来的 Mark Word。
                                1-0锁标志位200,表示轻量级锁状态。
                                重量级锁63-2Monitor 指针62指向 ObjectMonitor 实例(操作系统级互斥锁)。
                                1-0锁标志位210,表示重量级锁状态。
                                GC 标记63-2GC 相关信息62存储垃圾回收标记信息(如对象是否存活,具体由 GC 算法决定)。
                                1-0锁标志位211,表示 GC 标记状态。

                                32位JVM中是这么存的:

                                锁状态

                                25bit

                                4bit

                                1bit

                                2bit

                                23bit

                                2bit

                                是否偏向锁

                                锁标志位

                                无锁

                                对象的哈希码

                                分代年龄

                                0

                                01

                                偏向锁

                                线程ID

                                Epoch

                                分代年龄

                                1

                                01

                                轻量级锁

                                指向栈中锁记录的指针

                                00

                                重量级锁

                                指向重量级锁的指针

                                10

                                GC标记

                                11

                                通俗解释

                                • Mark Word 像一个多功能显示屏,显示的内容根据对象状态切换。
                                • 比如,对象没被锁时,显示“哈希码”和“GC年龄”;被锁住时,显示“锁信息”和“线程ID”。

                                2.2 Class Metadata Address

                                这部分是一个指针,指向对象所属类的元数据(Class Metadata),存储在JVM的方法区(或元空间)。元数据包含类的结构信息,比如:

                                • 类名、父类、接口。
                                • 方法表、字段表。
                                • 静态变量等。

                                通俗解释

                                • 就像身份证上的“籍贯”,告诉JVM这个对象是哪个类的实例。
                                • JVM通过这个指针找到类的“蓝图”,知道如何操作这个对象。

                                2.3 Array Length

                                如果对象是数组(比如pythonint[ ]),对象头会额外包含一个4字节的字段,记录数组的长度。普通对象没有这一部分。

                                通俗解释

                                • 数组就像一个货架,Array Length 告诉你货架上有多少格子。

                                三、对象头的作用和底层原理

                                3.1 对象头在锁机制中的作用

                                对象头是synchronized锁的核心,因为它存储了锁状态和Monitor信息。以下是synchronized锁的工作原理与对象头的关联:

                                1. 无锁状态

                                  • Mark Word 存储哈希码和GC年龄,锁标志位是01
                                  • 对象未被任何线程锁定。
                                2. 偏向锁

                                  • 当一个线程首次获取锁时,JVM将Mark Word中的线程ID设为该线程ID,锁标志位仍为01
                                  • 偏向锁假设锁通常被同一线程持有,减少锁获取的开销。
                                  • 例如:Mark Word 记录“线程A的ID”,下次线程A再获取锁时,直接检查ID,无需额外操作。
                                3. 轻量级锁

                                  • 如果有轻微竞争(比如另一个线程尝试获取锁),JVM将锁升级为轻量级锁。
                                  • Mark Word 存储一个指向锁记录的指针(如线程栈中的记录),锁标志位变为00
                                  • 锁记录保存了原来的Mark Word内容,释放锁时恢复。
                                4. 重量级锁

                                  • 如果竞争激烈(多个线程争抢锁),JVM将锁升级为重量级锁。
                                  • Mark Word 存储一个指向Monitor的指针,锁标志位变为10
                                  • Monitor 是一个操作系统级别的互斥锁(Mutex),管理线程的等待和唤醒。

                                通俗例子

                                • 想象对象是一个房间,Mark Word 是门上的锁。
                                • 无锁:门没锁,任何人都能进。
                                • 偏向锁:门上贴了“只许小明进”的标签,小明不用每次都开锁。
                                • 轻量级锁:小明和朋友轮流用钥匙开锁,稍微麻烦点。
                                • 重量级锁:请了个保安(Monitor)守门,所有人排队登记才能进。

                                3.2 对象头在垃圾回收中的作用

                                Mark Word 中的分代年龄(GC Age)用于JVM的分代垃圾回收(Generational GC):

                                • 每次对象在Minor GC中存活,年龄加1。
                                • 当年龄达到阈值(默认15),对象晋升到老年代
                                • Mark Word 的GC标记位(11)在GC期间用于标记对象是否存活。

                                通俗解释

                                • 分代年龄就像人的年龄,记录对象“活了多久”。
                                • JVM通过年龄判断对象是否“老了”,决定是否搬到“养老院”(老年代)。

                                3.3 对象头在哈希码中的作用

                                当调用对象的hashCode()方法时,JVM将哈希码存储在Mark Word中。如果对象被锁住,哈希码可能被暂时移到Monitor或锁记录中。

                                通俗解释

                                • 哈希码就像对象的“身份证号”,用于HashMap等场景。
                                • Mark Word 是身份证的“号码栏”,锁住时号码可能被临时抄到别处。

                                四、对象头的源码分析

                                对象头的实现主要在HotSpot JVM的C++代码中,位于src/hotspot/share/oops/目录。我们重点分析Mark Word和相关逻辑。

                                4.1 Mark Word的定义

                                在HotSpot JVM中,Mark Word 由markOop类表示,定义在markOop.hpp中:

                                // src/hotspot/share/oops/markOop.hpp
                                class markOopDesc : public oopDesc {
                                private:
                                  uintptr_t _value; // Mark Word的实际值(64位机器上是64位)
                                
                                public:
                                  // 获取锁状态
                                  inline uintptr_t lock_bits() const {
                                    return (_value & lock_mask_in_place);
                                  }
                                
                                  // 获取线程ID(偏向锁)
                                  inline uintptr_t biased_thread_id() const {
                                    return (_value >> biased_lock_thread_id_shift);
                                  }
                                
                                  // 获取哈希码
                                  inline uintptr_t hash() const {
                                    return (_value >> hash_shift) & hash_mask;
                                  }
                                
                                  // 获取分代年龄
                                  inline uintptr_t age() const {
                                    return (_value >> age_shift) & age_mask;
                                  }
                                };
                                

                                关键点解释

                                • _value 是一个64位整数,存储Mark Word的所有信息。
                                • 通过位运算(>>&)提取锁状态、线程ID、哈希码、年龄等。
                                • 锁标志位(最低2位)决定Mark Word的当前状态(01001011)。

                                4.2 对象头的内存布局

                                HotSpot JVM中,对象的内存布局由oopDesc类定义,位于oop.hpp:

                                // src/hotspot/share/oops/oop.hpp
                                class oopDesc {
                                private:
                                  volatile markOop _mark; // Mark Word
                                  Klass* _metadata;       // Class Metadata Address
                                };
                                
                                • _mark:Mark Word,存储锁状态等。
                                • _metadata:指向类元数据的指针。

                                如果对象是数组,还会额外包含数组长度字段(由JVM在分配内存时添加)。

                                4.3 锁状态的切换逻辑

                                锁状态的切换在 synchronizer.cpp 中实现,涉及偏向锁、轻量级锁、重量级锁的转换。以下是简化逻辑:

                                // src/hotspot/share/runtime/synchronizer.cpp
                                void ObjectSynchronizer::enter(Handle obj, BasicLock* lock, Thread* self) {
                                  markOop mark = obj->mark(); // 获取Mark Word
                                
                                  if (mark->is_neutral()) { // 无锁状态
                                    // 尝试偏向锁
                                    if (UseBiasedLocking) {
                                      markOop biased = mark->biased_to(self); // 设置线程ID
                                      if (Atomic::cmpxchg(biased, obj->mark_addr(), mark) == mark) {
                                        return; // 偏向成功
                                      }
                                    }
                                    // 尝试轻量级锁
                                    lock->set_displaced_header(mark); // 保存Mark Word到锁记录
                                    if (Atomic::cmpxchg((markOop)lock, obj->mark_addr(), mark) == mark) {
                                      return; // 轻js量级锁成功
                                    }
                                  }
                                
                                  // 升级到重量级锁
                                  ObjectMonitor* monitor = inflate_monitor(obj, self);
                                  monitor->enter(self); // 进入Monitor
                                }
                                

                                关键点解释

                                1. 无锁到偏向锁
                                  • 检查Mark Word是否为无锁(is_neutral())。
                                  • 用CAS(cmpxchg)将线程ID写入Mark Word。
                                2. 偏向锁到轻量级锁
                                  • 如果有竞争,撤销偏向锁(biased_to失败)。
                                  • 将Mark Word替换为锁记录指针,保存原Mark Word到锁记录。
                                3. 轻量级锁到重量级锁
                                  • 如果竞争加剧,创建Monitor(inflate_monitor)。
                                  • Mark Word指向Monitor,进入操作系统级锁。

                                4.4 Monitor与对象头的交互

                                重量级锁依赖ObjectMonitor类(objectMhttp://www.devze.comonitor.hpp),Mark Word存储Monitor指针:

                                class ObjectMonitor {
                                private:
                                  Thread* _owner; // 持有锁的线程
                                  markOop _header; // 保存原来的Mark Word
                                  // ...
                                };
                                
                                • 当锁升级为重量级锁,Mark Word指向ObjectMonitor实例。
                                • 释放锁时,ObjectMonitor将保存的 _header(原Mark Word)恢复到对象头。

                                五、对象头的内存开销和优化

                                5.1 内存开销

                                在64位JVM中,对象头的典型大小:

                                • 普通对象:8字节(Mark Word)+ 8字节(Class Metadata Address)= 16字节。
                                • 数组对象:16字节 + 4字节(Array Length)= 20字节。

                                这意味着即使一个空对象(无字段)也有16字节的开销,主要来自对象头。

                                5.2 指针压缩

                                为了减少内存开销,HotSpot JVM支持指针压缩(-XX:+UseCompressedOops):

                                • 将64位指针压缩为32位(通过偏移编码)。
                                • 压缩后,对象头大小变为:
                                  • 普通对象:8字节(Mark Word)+ 4字节(压缩的Class Metadata Address)= 12字节。
                                  • 数组对象:12字节 + 4字节(Array Length)= 16字节。

                                通俗解释

                                • 指针压缩就像把“详细地址”的省市县简化成“邮编”,节省空间。

                                5.3 锁优化

                                对象头的锁状态切换(偏向锁 → 轻量级锁 → 重量级锁)是JVM的性能优化:

                                • 偏向锁:适合单线程场景,Mark Word直接记录线程ID,获取锁几乎无开销。
                                • 轻量级锁:适合低竞争场景,用CAS操作锁记录,减少系统调用。
                                • 重量级锁:适合高竞争场景,依赖操作系统Mutex,但开销大。

                                六、完整流程

                                1. 对象头的定义

                                  • 每个Java对象在内存中包含对象头,分为Mark Word、Class Metadata Address、Array Length(可选)。
                                  • Mark Word 是动态部分,存储锁状态、哈希码、GC年龄等。
                                2. 对象头的作用

                                  • 锁机制:通过Mark Word实现synchronized的偏向锁、轻量级锁、重量级锁。
                                  • 垃圾回收:记录分代年龄,支持分代GC。
                                  • 类型信息:指向类元数据,确定对象类型。
                                  • 哈希码:存储hashCode()值。
                                3. 底层实现

                                  • Mark Word 由markOop类管理,通过位运算提取信息。
                                  • 锁状态切换由synchronizer.cpp实现,涉及CAS和Monitor。
                                  • 对象头与JVM的内存管理和线程调度紧密协作。
                                4. 内存优化

                                  • 指针压缩减少对象头大小(16字节 → 12字节)。
                                  • 锁优化(偏向锁、轻量级锁)降低锁开销。

                                七、通俗总结

                                • 对象头是什么?它是对象的“身份证”,记录锁状态、类型信息、年龄等。
                                • 怎么工作?Mark Word 像一个多功能显示屏,根据对象状态切换显示内容(锁、哈希码等)。
                                • 为什么重要?没有对象头,JVM无法实现锁、垃圾回收等核心功能。
                                • 底层实现?通过HotSpot JVM的C++代码(markOop、synchronizer)管理,依赖位运算和操作系统支持。

                                生活化比喻

                                • 对象头就像一个智能门牌,平时显示房间号(类型信息)、住户ID(锁状态)、房屋年龄(GC年龄)。
                                • 当有人敲门(线程访问),门牌会切换显示“谁能进”(锁信息),还能记录“敲门次数”(哈希码)。

                                八、扩展阅读

                                1. 源码推荐
                                  • HotSpot JVM:markOop.hpp(Mark Word定义)、synchronizer.cpp(锁逻辑)。
                                  • 相关类:oop.hpp(对象布局)、objectMonitor.hpp(Monitor实现)。
                                2. 工具
                                  • 用jmap -histo查看对象内存占用,分析对象头开销。
                                  • 用jol(Java Object Layout)库查看对象头结构。
                                3. 书籍
                                  • 《深入理解Java虚拟机》(周志明):深入讲解JVM内存和对象头。
                                  • 《Java并发编程实战》:结合锁机制理解对象头。

                                到此这篇关于Java的对象头原理与源码超详细讲解的文章就介绍到这了,更多相关Java对象头原理详解内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

                                0

                                上一篇:

                                下一篇:

                                精彩评论

                                暂无评论...
                                验证码 换一张
                                取 消

                                最新开发

                                开发排行榜