Java源碼分析 | Object

本文基於 OracleJDK 11, HotSpot 虛擬機。

Object 定義

Object 類是類層次結構的根。每個類都有 Object 類作為超類。所有對象,包括數組等,都實現了這個類的方法。

靜態程式碼塊

在Object類的最開始部分,有如下四行程式碼:

private static native void registerNatives();
static {
    registerNatives();
}

native 方法主要用於通過調用 C 或 C++ 實現的本地方法來對底層作業系統的訪問。

擴展

native 關鍵字說明其修飾的方法是一個原生態方法,方法對應的實現不是在當前文件,而是在用其他語言(如 C 和C++)實現的文件中。

Java 語言本身不能對作業系統底層進行訪問和操作,但是可以通過 JNI(Java Native Interface)介面調用其他語言來實現對底層的訪問。

**JNI **全稱是 Java Native Interface,即 Java 本機介面,是 Java 和 Native 間的通訊橋樑。Java 調用 Native,可以去調用非 Java 實現的庫,擴充 Java 的使用場景;反之 Native 調用 Java,可以在別的語言裡面調用 Java。

類的 static 靜態程式碼塊會在類初始化時調用,其目的是為該類中包含的除了registerNatives()方法以外的所有本地方法(被 native 關鍵字修飾的方法)進行註冊。

構造函數

 @HotSpotIntrinsicCandidate
 public Object() {}
  • 無參構造函數主要是創建一個新的 Object 對象。
  • @HotSpotIntrinsicCandidate 註解特定於 HotSpot 虛擬機,它表明帶註解的方法可能被 HotSpot 內在化,在虛擬機記憶體在更高效的實現來替換被註解修飾的方法以提高性能。該註解是 Java 庫內部的,與應用程式沒有任何關聯。

方法

getClass()

@HotSpotIntrinsicCandidate
public final native Class<?> getClass();

該方法是 native 方法,作用是返回當前對象運行時的類。返回的 Class 對象是被static synchronized方法鎖定的對象。

實際的結果類型是 Class<? extends |X|>,X 是對調用 getClass 的表達式的靜態類型擦除。如下所示,程式碼中不需要強制轉換:

Number n = 0; 
Class<? extends Number> c = n.getClass(); 

Class 對象表示運行時此對象的類。

hashCode()

public native int hashCode();

該方法是 native 方法,作用是返回當前對象的哈希碼值。支援此方法是為了便於對 java.util.HashMap 等提供的哈希表。

hashCode 的通用規則是:

  • 每當在 Java 應用程式執行期間對同一對象多次調用它時,該方法始終返回相同的整數。如果 equals() 中使用的資訊沒有被修改。從應用程式的一次執行到相同的應用程式的一次執行,此整數不必保持一致。
  • 如果兩個對象根據調用 equals() 方法相等,則在每個對象上調用 hashCode() 方法必須產生相同的整數結果。
  • 如果兩個對象根據調用 equals()方法不相等,在每個對象上調用hasCode()方法不要求必須產生不同的整數結果(因為可能存在哈希碰撞)。

equals()

public boolean equals(Object obj) {
        return (this == obj);
}

該方法指示其他對象是否「等於」當前對象,判斷兩個對象是否具有相同的引用(對象的記憶體地址)。

equals() 函數必須滿足以下五點條件:

  • 反身性:對於任何 x, x.equals(x) 應該返回 true。
  • 對稱性:對於任何 x 和 y,x.equals(y) 應該返回 true 當且僅當 y.equals(x) 返回 true 。
  • 傳遞性:對於任何 x,y, 還有 z,如果 x.equals(y) 返回 true 並且 y.equals(z) 返回 true,那麼 x.equals(z) 應該返回 true。
  • 一致性:對於任何 x 和 y,在對象沒有被改變的情況下,多次調用 x.equals(y) 應該總是返回 true 或者 false。
  • 對於任何非 null 的 x,x.equals(null) 應該返回 false。

類 Object 的equals()方法在對象上實現了最有區別的等價關係,也就是說,對於任何非空引用值 x 和 y,當且僅當 x 和 y 引用的是同一對象的時候,x==y 返回 true。

默認情況下從超類 Object 繼承而來的 equals() 方法與 == 是完全等價的,比較的都是對象的記憶體地址,但我們可以重寫 equals()方法,使其按照我們的需求的方式進行比較。

注意

每當重寫 equals()方法時,通常都需要重寫hashCode()方法,以便維護hashCode()方法的常規約定,該方法申明相等的對象必須具有相同的 hashCode 值。如果子類不重寫,將會默認使用父類對象的 hashCode() 方法,導致對象內容一致但對象記憶體地址不一致。

因為equals()方法比較消耗性能且效率低,一般有大量數據需要快速的對比的話,會先比對hashCodehashCode相等的再使用equals進行比較。

Java 中常見的一些默認類都會重寫 hashCode()equals() 方法,如 String 類等。

clone()

 @HotSpotIntrinsicCandidate
 protected native Object clone() throws CloneNotSupportedException;

該方法是 native 方法,作用是創建並返回此對象的副本。

注意:clone 方法本身沒有實現 Cloneable 介面,但在調用 clone 方法時需要實現 Cloneable 介面並重寫,否則會拋出 CloneNotSupportedException 異常以表示無法克隆。

通常重寫克隆需要滿足以下條件:

  • x.clone() != x 為 true (對象引用指向堆記憶體地址不同)
  • x.clone().getClass() == x.getClass() 為 true(相同的運行時類)
  • x.clone().equals(x) 為 true (對象屬性內容相同,由於 Object 的 equals() 默認為 this == obj,所以需要重寫)

但這不是絕對的,因為淺克隆和深克隆的區別。

擴展

淺克隆

被複制對象的所有變數都含有與原來的對象相同的值,而所有的對其他對象的引用仍然指向原來的對象。換而言之,淺拷貝僅僅複製所拷貝的對象,而不複製它所引用的對象.

深克隆

被複制對象的所有變數都含有與原來的對象相同的值,而那些引用其他對象的變數將指向被複制過的新對象,而不再是原有的那些被引用的對象。換而言之,深拷貝把要複製的對象所引用的對象都複製了一遍。

toString()

返回對象的字元串表示形式。

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

此方法默認返回 類名@無符號十六進位表示形式組成對象的哈希碼。該方法是為了更簡潔清晰且資訊豐富的表示對象內容,易於閱讀,所以建議所有子類重寫該方法。

notify()、notifyAll()

@HotSpotIntrinsicCandidate
public final native void notify();

@HotSpotIntrinsicCandidate
public final native void notifyAll();

這兩個方法都是 native 方法,作用都是喚醒正在此對象的監視器上等待的執行緒,區別在於 notify() 是喚醒單執行緒,而 notifyAll()是喚醒所有執行緒。

如果有任何執行緒正在等待該對象,則選擇其中一個被喚醒。該選擇是任意的,並由實施自行決定。執行緒通過調用 wait 方法之一在對象的監視器上等待。

在當前執行緒放棄對該對象的鎖定之前,被喚醒的執行緒將無法繼續。被喚醒的執行緒將以通常的方式與可能正在積極競爭以在此對象上同步的任何其他執行緒競爭

該方法只能由作為該對象監視器所有者的執行緒調用,否則會拋出 IllegalMonitorStateException 異常。

擴展

每個對象都有一個」鎖「,即監視器 Monitor,而且每個對象都有一個同步隊列(EntrySet)和等待隊列(WaitSet),同步隊列和等待隊列裡面都存放著執行緒對象的引用。

notify() 是對 notifyAll() 的一個優化,但它有很精確的應用場景,並且要求正確使用。不然可能導致死鎖。正確的場景應該是 WaitSet 中等待的是相同的條件,喚醒任一個都能正確處理接下來的事項,如果喚醒的執行緒無法正確處理,務必確保繼續 notify() 下一個執行緒,並且自身需要重新回到 WaitSet 中。

wait()、wait(long timeout)、wait(long timeout, int nanos)

 public final void wait() throws InterruptedException {
        wait(0L);
 }
public final native void wait(long timeoutMillis) throws InterruptedException;
     
public final void wait(long timeoutMillis, int nanos) throws InterruptedException {
        if (timeoutMillis < 0) {
            throw new IllegalArgumentException("timeoutMillis value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos > 0) {
            timeoutMillis++;
        }

        wait(timeoutMillis);
    }

wait(long timeout) 方法是 native 方法,作用是讓當前執行緒釋放 CPU 佔用資源並且釋放對象的」鎖「,直到該對象執行了 notify()/noyifyAll()方法,或者過了 timeoutMillis 的等待時間(單位:毫秒),或者被其他執行緒調用了該執行緒的 interrupt()方法打斷,該執行緒會被喚醒。

wait()方法就是調用了 wait(0L),代表無限等待,只能通過該對象執行了 notify()/noyifyAll()方法、被打斷,該執行緒會被喚醒。

wait(long timeoutMillis, int nanos) 方法只是添加了一個範圍在0~999999納秒的附加時間int nanos,本質上還是調用了 wait(long timeout)

如果當前執行緒不是對象監視器的所有者,則會拋出 IllegalMonitorStateException 異常。

如果當前執行緒在等待之前或期間被任何執行緒中斷,則會拋出 InterruptedException 異常。

執行緒也可以在沒有被通知、中斷或超時的情況下喚醒,即所謂的虛假喚醒。雖然這在實踐中很少發生,但應用程式必須通過測試應該導致執行緒被喚醒的條件來防範它,可以將 wait 配合 while 循環使用,蘇醒後進行條件檢查,如果不滿足則 繼續 wait() 直至條件滿足再往下執行。

finalize()

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

當垃圾收集確定不再有對該對象的引用時,由對象上的垃圾收集器調用。子類覆蓋finalize()方法來處理系統資源或執行其他清理。

每個對象的 finalize() 方法只能被系統執行一次,該方法類似析構函數但不等價。

死亡逃逸,可以在對象被回收之前在 finalize 方法裡面重新與其他對象(其他對象不能是即將被回收的對象)建立關聯即可,但機會只有一次。

在JDK9及其之後已被廢棄使用。原因是最終確定機制本質上是有問題的。最終確定會導致性能問題、死鎖和掛起。終結器中的錯誤可能導致資源泄漏;如果不再需要,則無法取消最終確定;並且在對不同對象的 finalize 方法的調用之間沒有指定順序。此外,無法保證最終確定的時間。finalize() 方法可能僅在無限期延遲之後才在可終結對象上調用

對象持有非堆資源的類應該提供一種方法來啟用這些資源的顯式釋放,它們可以實現 java.lang.AutoCloseable

java.lang.ref.Cleanerjava.lang.ref.PhantomReference 提供了更靈活、更有效的方法來在對象變得無法訪問時釋放資源。

Tags: