java.lang.Object 是 Java 类型体系的根:除它自身外,每个类(含数组类型)最终都继承它,因而它定义的方法构成「所有对象共有的最小行为集」1。下文依据本机 Oracle JDK 17.0.20.1($JAVA_HOME=/home/charlie/workspace/sdks/jdk-17)自带源码包中的 Object.java,按成员出现顺序解析——侧重为何存在、契约与 HotSpot 如何支撑、工程上如何用;源码与本机实测用来锚定结论,而不是逐行注释。

源码路径:$JAVA_HOME/lib/src.zip → java.base/java/lang/Object.java1。

运行与验证:同一套 Oracle JDK;工程 learning-lab/java/java17-sources 中的 ObjectLayoutVerify / ObjectMethodsVerify / ObjectMonitorVerify2。

类注释:根类型意味着什么

java
1/**
2 * Class {@code Object} is the root of the class hierarchy.
3 * Every class has {@code Object} as a superclass. All objects,
4 * including arrays, implement the methods of this class.
5 *
6 * @see     java.lang.Class
7 * @since   1.0
8 */
9public class Object {

把「万物皆对象」落成语言规则,有三层后果:

  1. 统一多态入口:任意引用都能当成 Object 传递,集合、反射、序列化等基础设施才能写通用逻辑。
  2. 共享行为面:相等、哈希、字符串表示、监视器等待/通知、浅拷贝入口都挂在根上,子类按需覆盖。
  3. 数组也是对象:int[]、String[] 有独立的运行时 Class,同样具备上述方法;数组还对 clone() 有特殊约定(见后文)。

JDK 17 这份 Object.java 里没有早期版本常见的 registerNatives 静态块;本地方法由 VM 侧绑定1。这只是源文件形态上的事实,不影响对外 API。


无参构造 Object():创建与初始化的分工

java
1@IntrinsicCandidate
2public Object() {}

设计意图

Object 没有实例字段,构造器为空体。对象能否被使用,关键不在这段 Java 代码,而在 new 字节码对应的分配与对象头安装。构造器表达的是「初始化逻辑」;对空 Object 而言,初始化无可写内容。

原理:new 在 HotSpot 里做什么

执行 Object obj = new Object(); 时,HotSpot 侧典型顺序是:

  1. 在堆上分配对齐后的内存;
  2. 写入对象头:Mark Word(初始 unlocked、尚未计算 identity hash)与 Klass 指针;
  3. 调用 <init>(此处为空);
  4. 得到对象引用。

因此构造器执行时,对象已经在堆上,头信息已经可用。@IntrinsicCandidate 表示 HotSpot 可以把该方法换成手写高效实现(intrinsic);注解不改变语言语义,是否替换由 JVM 与平台决定1。

对象头为何长这样

64 位 HotSpot(本机 Oracle JDK)在开启压缩类指针时,空 Object 经 JOL 观察为:8 字节 Mark + 4 字节压缩 Klass + 4 字节对齐填充 = 16 字节。默认对象对齐是 8 字节(-XX:ObjectAlignmentInBytes=8),所以「实例 16 字节」来自 12 字节头再 pad,而不是「一律 16 字节对齐」32。

Mark Word 在 64 位 HotSpot 上承载锁态、分代年龄、identity hash 等信息。结合本机 JOL 与公开的 HotSpot 对象布局说明,无锁普通对象大致可理解为(高位在左)3:

text
1unused | hash(最多31 bit) | gap | age:4 | biased_lock:1 | lock:2

锁态低 2 位常见含义:01 unlocked、00 轻量锁、10 monitor、11 marked。本机新建对象 Mark 为 0x1,JOL 标注 non-biasable; age: 0,与「默认未启用偏向锁、尚未计算 hash」一致2。

Identity hash 首次算出后写入 Mark 的 hash 域,供默认 hashCode / identityHashCode 稳定读取;JOL 在调用 hashCode() 后会打印出 hash: 0x…32。

用法与验证

日常几乎不会 new Object() 做业务建模;更常见的是理解「任意对象都有对象头」,从而解释锁、hash、GC age 等信息从何而来。

ObjectLayoutVerify(JOL)在本机 Oracle JDK 上再次运行得到(identity hash 每次进程不同,下列为当次输出)2:

text
 1===== new Object() 后 =====
 2java.lang.Object object internals:
 3OFF  SZ   TYPE DESCRIPTION               VALUE
 4  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
 5  8   4        (object header: class)    0x00000d68
 6 12   4        (object alignment gap)
 7Instance size: 16 bytes
 8
 9===== hashCode()=1149226626 (0x447fce82) 后 =====
10java.lang.Object object internals:
11OFF  SZ   TYPE DESCRIPTION               VALUE
12  0   8        (object header: mark)     0x000000447fce8201 (hash: 0x447fce82; age: 0)
13  8   4        (object header: class)    0x00000d68
14 12   4        (object alignment gap)
15Instance size: 16 bytes

new 后 Mark 为 0x1(尚未写入 hash);调用 hashCode() 后同一 Mark 出现 hash: 0x447fce82,且返回值与该字段一致。Klass 压缩值与对齐填充在两次快照间不变。


getClass():运行时类型从哪里来

java
1@IntrinsicCandidate
2public final native Class<?> getClass();

设计意图

类型系统在编译期用静态类型做检查,运行时仍需知道「堆上究竟是谁」。getClass 提供稳定、不可覆盖的运行时类型查询,供反射、日志、按真实类型分支等使用。Javadoc 还标明:返回的 Class 正是该类 static synchronized 方法所锁定的对象1。

原理

  • final native:子类不能改语义;VM 从对象头 Klass 映射到对应的 java.lang.Class 实例,常作 intrinsic。
  • 源码签名是 Class<?>,但编译器按 JLS 将调用处类型当作 Class<? extends |X|>,其中 |X| 是接收表达式静态类型的擦除4。因此 Number n; n.getClass() 可直接赋给 Class<? extends Number>。
  • 实例 synchronized 锁 this;static synchronized 锁该类的 Class 对象——与「用 Foo.class 或该类实例的运行时 Class」描述的是同一把类锁(对具体实例,getClass() 给出的是该实例的真实类型对应的 Class)。

用法

场景更合适的选择
需要实例真实类型obj.getClass()
静态上下文、字面指定类型Foo.class
泛型擦除后的静态类型注意 List<String> 擦除后是 List,返回类型不是 Class<? extends List<String>>

ObjectMethodsVerify 本机输出2:

text
1===== getClass =====
2n.getClass()=java.lang.Integer
3list.getClass()=java.util.ArrayList
4list.getClass() == ArrayList.class: true

hashCode():为哈希表准备的整数指纹

java
1@IntrinsicCandidate
2public native int hashCode();

设计意图

哈希表(如 HashMap)用整数把键分散到桶里,再在桶内用 equals 确认。hashCode 就是这枚指纹。契约写在 Javadoc 里,目的是保证「相等对象能进同一查找路径」,并允许冲突存在1。

原理:契约为何如此

  1. 同一次运行内一致(参与 equals 的信息未改):否则同一键刚 put 完就可能 get 不到。跨进程不要求相同,否则会限制实现(例如不能用与地址/随机相关的策略)。
  2. equals 为 true ⇒ hash 必须相同:桶索引先依赖 hash;违反则逻辑相等的键落在不同桶。
  3. equals 为 false 不要求 hash 不同:冲突合法;区分度越高,链表/树化压力越小。

@implSpec:默认实现在合理范围内尽量让不同对象得到不同整数。在本机 Oracle HotSpot 上,未重写的 hashCode 与 System.identityHashCode 一致:首次计算后 identity hash 写入对象 Mark Word,之后稳定读取(JOL 可见 hash: 字段);并非简单「把堆地址截断当 hash」32。对象处于加锁等状态时,hash 仍须在逻辑上保持「算过就不变」(具体存放位置由 VM 处理)。

未重写时,默认 hash 标识的是对象身份;已重写的类型(如 String)走自己的内容哈希,与默认路径无关。

用法

  • 按字段定义逻辑相等时,用同一组字段同时实现 equals 与 hashCode(如 Objects.hash(...))。
  • 需要始终拿 identity hash(即使子类重写了 hashCode)时用 System.identityHashCode。

ObjectMethodsVerify 本机输出2:

text
1===== hashCode / identityHashCode =====
2a.hashCode() twice same: true
3a.hashCode()==b.hashCode(): false
4identityHashCode(a)==a.hashCode(): true
5===== 只重写 equals 不重写 hashCode =====
6p1.equals(p2)=true
7p1.hashCode()=254961745
8p2.hashCode()=820795191
9map.get(p2)=null

equals 为 true,但默认 hashCode(identity)不同 → get 为 null。

HashMap 定位桶时会对 hashCode 做扰动(h ^ (h >>> 16)),再 (n-1) & hash;这是表实现细节,强化了「hash 分布」对性能的影响。


equals(Object):等价关系的根实现

java
1public boolean equals(Object obj) {
2    return (this == obj);
3}

设计意图

默认实现采用引用相等:只有同一个对象才相等。这是最细的等价关系——每个等价类通常只有一个元素。内容相等由子类按领域重写;根类型先给出一个永远满足契约、且与 identity hash 自然一致的基线1。

原理:五条契约各自防什么

Javadoc 要求非空引用上的等价关系:

性质若破坏,典型后果
自反 x.equals(x)集合「含自身」类逻辑异常
对称HashSet/HashMap 一侧能找到、另一侧找不到
传递分组、去重语义崩坏
一致未改关键字段时结果漂移,结构损坏
x.equals(null)==false调用方可依赖不抛 NPE 的约定

@apiNote:重写 equals 时通常必须重写 hashCode,否则破坏「相等 ⇒ 同 hash」1。

继承 + instanceof 时容易伤对称性:父类只比部分字段、子类要求更严,会出现 p.equals(cp) != cp.equals(p)。若子类在对方是父类时「放宽」比较,又常伤传递性。Effective Java 建议对值类型谨慎做可继承的 equals,或用 getClass() 限制同类型,或改用组合5。

用法

本机 ObjectMethodsVerify2:

text
1===== equals 默认 =====
2x.equals(x)=true
3x.equals(y)=false
4x.equals(null)=false
5===== equals 对称性破坏 =====
6p.equals(cp)=true
7cp.equals(p)=false

同类型字段相等的常见写法:

java
 1@Override
 2public boolean equals(Object o) {
 3    if (this == o) return true;
 4    if (o == null || getClass() != o.getClass()) return false;
 5    Person other = (Person) o;
 6    return age == other.age && Objects.equals(name, other.name);
 7}
 8
 9@Override
10public int hashCode() {
11    return Objects.hash(name, age);
12}

clone():浅拷贝入口

java
1@IntrinsicCandidate
2protected native Object clone() throws CloneNotSupportedException;

设计意图

提供「复制自身」的钩子:默认语义是浅拷贝——新实例字段按赋值拷贝,引用字段仍指向原对象内部结构。是否允许拷贝由 Cloneable 标记;Object 自身未实现该接口1。

原理

  1. 未实现 Cloneable → CloneNotSupportedException;数组类型视为实现了 Cloneable,且 T[].clone() 返回 T[]。
  2. 实现了则分配同运行时类实例并拷贝字段(浅)。
  3. 惯例(非绝对硬性):clone() != this、clone().getClass() == getClass();一路调用 super.clone() 更易满足后者。

VM 侧按对象大小做内存级拷贝并安装对象头;Java 层负责门禁与约定。Cloneable 是标记接口,没有 clone 方法本身,设计上常被批评;许多代码改用拷贝构造或静态工厂,尤其需要深拷贝时更清晰5。

用法

java
 1final class Cell implements Cloneable {
 2    int value;
 3    @Override
 4    public Cell clone() {
 5        try {
 6            return (Cell) super.clone();
 7        } catch (CloneNotSupportedException e) {
 8            throw new AssertionError(e);
 9        }
10    }
11}

本机 ObjectMethodsVerify2:

text
 1===== clone / toString =====
 2clone != original: true
 3same class: true
 4value copied: true
 5after mutate original, copy.value=7
 6array clone independent: true
 7toString=java.lang.Object@553e7138
 8expectedPrefix=java.lang.Object@553e7138
 9toString matches formula: true
10===== 未实现 Cloneable 时 super.clone 失败 =====
11clone failed: CloneNotSupportedException

toString():给人看的默认描述

java
1public String toString() {
2    return getClass().getName() + "@" + Integer.toHexString(hashCode());
3}

设计意图

日志、调试、异常信息里需要一串可读文本。默认实现不尝试描述业务字段,只给出「类型名 + identity 向哈希的十六进制」,保证任意对象都能打印且实现廉价1。

原理与用法

公式依赖当前 hashCode():未重写时即 identity hash 的 hex;重写了 hashCode 则跟内容哈希走。Javadoc 建议子类给出更有信息量的表示,且不保证跨时间、跨 JVM 稳定。

领域模型、DTO、作为日志关键字的对象通常应重写;纯内部哨兵对象可保留默认。上节实测中 toString 与公式拼接一致(当次为 java.lang.Object@553e7138)2。


对象监视器:wait / notify 的共享模型

notify、notifyAll、wait 都建立在对象监视器上:

  • 成为持有者的途径:实例 synchronized 方法、synchronized (obj)、或对该 Class 执行 static synchronized1。
  • 同一时刻至多一个线程持有某对象的 monitor。
  • HotSpot 用 Mark Word 编码锁态,竞争加剧时可膨胀为重量级监视器;等待线程进入 wait set,通知从 wait set 中唤醒3。
  • 未持有 monitor 时调用这些方法 → IllegalMonitorStateException。

本机 ObjectMonitorVerify2:

text
1===== 未持锁调用 wait → IMSE =====
2caught: IllegalMonitorStateException

notify():唤醒一个等待者

java
1@IntrinsicCandidate
2public final native void notify();

设计意图与原理

在条件可能已满足时,让某一个在该对象上 wait 的线程离开 wait set。选哪个由实现决定,不可依赖顺序。被唤醒线程不会立刻执行临界区:必须等通知方释放 monitor 后,再与其它线程正常竞争锁——否则会破坏互斥1。

用法

适合「确定只有一类等待条件、唤醒一个即可」的场景。多条件共享一把锁时,误唤醒无关等待者的风险更高,往往更宜 notifyAll + while 检查条件。

本机 ObjectMonitorVerify2:

text
1===== notify 唤醒 =====
2waiter: entering wait
3main: notify
4waiter: resumed after notify

notifyAll():唤醒全部等待者

java
1@IntrinsicCandidate
2public final native void notifyAll();

设计意图与用法

语义同 notify 的持锁前提与「唤醒后仍要抢锁」;差别是清空(唤醒)该对象 wait set 上的所有线程。多等待者、条件谓词不同时,广播再各自用 while 过滤,正确性通常更好,代价是更多线程被调度起来抢锁。


wait():无限期等待的入口

java
1public final void wait() throws InterruptedException {
2    wait(0L);
3}

设计意图与原理

无参 wait 表示「不按超时醒来」,一直等到通知、中断或虚假唤醒等。实现上直接委托 wait(0L);与带 nanos 重载的规范一致:超时参数全 0 表示忽略时间维度1。

调用方仍须持有 monitor。异常:IllegalMonitorStateException;InterruptedException(抛出时清除中断状态)。

用法

生产代码几乎总是 while (!condition) { obj.wait(); },与下一节相同,以消化虚假唤醒与「通知时条件又变」的情况。


wait(long timeoutMillis):带超时的等待

java
1public final native void wait(long timeoutMillis) throws InterruptedException;

原理

在持有 monitor 的前提下:

  1. 进入该对象的 wait set;
  2. 释放本对象上的同步声明(其它对象上已持有的锁不释放);
  3. 暂停直至 notify/notifyAll、中断、超时或虚假唤醒;
  4. 重新获得 monitor 后,恢复进入 wait 前的锁状态,再返回。

timeoutMillis == 0 与无参类似(不因超时返回);> 0 则大约在指定毫秒后可返回。负值不合法。Javadoc 将其行为与 wait(timeoutMillis, 0) 对齐说明1。

本机 ObjectMonitorVerify2:

text
1===== wait(timeout) 超时返回 =====
2wait(50) returned, elapsedMs≈50

wait(long, int):纳秒参数与实现策略

java
 1public final void wait(long timeoutMillis, int nanos) throws InterruptedException {
 2    if (timeoutMillis < 0) {
 3        throw new IllegalArgumentException("timeoutMillis value is negative");
 4    }
 5    if (nanos < 0 || nanos > 999999) {
 6        throw new IllegalArgumentException(
 7            "nanosecond timeout value out of range");
 8    }
 9    if (nanos > 0 && timeoutMillis < Long.MAX_VALUE) {
10        timeoutMillis++;
11    }
12    wait(timeoutMillis);
13}

设计意图与原理

对外提供毫秒 + 纳秒的超时接口;底层 wait(long) 只有毫秒粒度。源码策略是:参数合法后,若 nanos > 0 且毫秒未达 Long.MAX_VALUE,则将毫秒加一再下传——把不足一毫秒的部分向上取整,而不是丢弃纳秒信息后假装仍是原毫秒值1。规范叙述的总时长约为 1000000L * timeoutMillis + nanos 纳秒;两者都为 0 则忽略时间。

本机 ObjectMonitorVerify2:

text
1===== wait(long,int) 参数校验 =====
2wait(-1,0): timeoutMillis value is negative
3wait(0,1000000): nanosecond timeout value out of range

用法:条件等待模板

虚假唤醒与「醒了条件仍不成立」都必须由应用处理1:

java
1synchronized (obj) {
2    while (!condition) {
3        obj.wait(timeoutMillis, nanos);
4    }
5    // 条件已成立
6}

finalize():已弃用的终结钩子

java
1@Deprecated(since="9")
2protected void finalize() throws Throwable { }

设计意图与原理

历史上用于对象不可达后做清理。Object 的实现为空。语言与实现都不保证调用时机、线程、跨对象顺序;同一对象至多终结一次;终结中未捕获异常被忽略;甚至可能在 finalize 里再次发布 this 造成「复活」。这些问题使 finalization 难以用于可靠资源管理,故自 Java 9 起正式废弃16。

子类若覆盖,不会自动链式调用父类 finalize,需要时显式 super.finalize()(通常放在 finally)。

用法(JDK 17 仍保留符号)

堆外资源优先:

  • 显式释放 + AutoCloseable / try-with-resources;
  • java.lang.ref.Cleaner 或 PhantomReference 等。

新代码以这些机制为主;finalize 仅在阅读遗留代码或理解 JLS 终结语义时仍会遇到。


成员索引(对照 Object.java)

源码成员形态本节
类 Javadoc注释类注释
Object()intrinsic 空构造无参构造
getClass()final nativegetClass
hashCode()nativehashCode
equals(Object)return this == objequals
clone()protected nativeclone
toString()Java 拼接toString
notify() / notifyAll()final nativenotify / notifyAll
wait() / wait(long) / wait(long,int)委托 / native / 校验+进位wait 起三节
finalize()@Deprecated 空体finalize

参考文献


  1. Oracle JDK 17.0.20.1,$JAVA_HOME/lib/src.zip 内 java.base/java/lang/Object.java(本机 $JAVA_HOME=/home/charlie/workspace/sdks/jdk-17;release 中 JAVA_RUNTIME_VERSION=17.0.20.1+1-LTS-4,IMPLEMENTOR=Oracle Corporation)。 ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  2. 验证环境:Oracle JDK 17.0.20.1(HotSpot,java.vendor=Oracle Corporation,$JAVA_HOME=/home/charlie/workspace/sdks/jdk-17)。工程 learning-lab/java/java17-sources;类 ObjectLayoutVerify、ObjectMethodsVerify、ObjectMonitorVerify。文中粘贴的输出均为该环境下重新执行 mvn … exec:java 的当次结果(identity hash / toString 后缀随进程变化)。 ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  3. Aleksey Shipilëv, Java Objects Inside Out(HotSpot 对象布局、identity hash 与 Mark Word). https://shipilev.net/jvm/objects-inside-out/ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  4. James Gosling et al., The Java Language Specification, Java SE 17 Edition, §15.8.2 Class Literals. https://docs.oracle.com/javase/specs/jls/se17/html/jls-15.html#jls-15.8.2 ↩︎

  5. Joshua Bloch, Effective Java, 3rd ed., Addison-Wesley, 2018. 相关条目:equals / hashCode;谨慎使用 Cloneable. ↩︎ ↩︎

  6. The Java Language Specification, Java SE 17 Edition, §12.6 Finalization of Class Instances. https://docs.oracle.com/javase/specs/jls/se17/html/jls-12.html#jls-12.6 ↩︎