Java - Obejct

關於Object類(Java 10)中的方法,根據其所涉及的知識點,分爲以下4個部分:html

  • 基礎
  • 反射
  • 線程
    • wait
      • public final void wait() throws InterruptedException
      • public final void wait​(long timeout) throws InterruptedException
      • public final void wait​(long timeout,int nanos) throws InterruptedException
    • notify : public final void notify()
    • notifyAll : public final void notifyAll()
  • JVM GC
    • finalize : protected void finalize() throws Throwable

 2018-09-15java

今天寫:equals、hashCode、toString、clone、getClass,後面的方法等學到相關知識再做補充。sql

 一. equals

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

equals用來判斷兩個對象是否」相等「,而對「相等」這個詞的定義不一樣,得出的結果也不一樣:編程

  • 1. Object類中的equals使用 this == obj 來定義相等,即:兩個引用是否指向同一個對象;
  • 2. 有些時候,咱們並不須要定義如此嚴格的相等,好比:定義一個Point類,只要兩個點的橫縱座標分別相等,咱們就認爲它們相等;

要實現上面的需求,咱們就須要重寫equals方法,以下:api

 1 public class Point {
 2     int x;
 3     int y;
 4     
 5     Point(int x, int y) {
 6         this.x = x;
 7         this.y = y;
 8     }
 9     
10     @Override
11     public boolean equals(Object obj) {
12         if (this == obj) {
13             return true;
14         }
15         if (obj instanceof Point) {
16             Point p = (Point)obj;
17             return (this.x == p.x && this.y == p.y);
18         }
19         return false;
20     }
21 }
View Code

測試:安全

 1 public static void main(String[] args) {
 2     Point p1 = new Point(1, 1);
 3     Point p2 = new Point(1, 1);
 4     System.out.println("p1.equals(p2) : " + p1.equals(p2));
 5     p2.x = 2;
 6     System.out.println("p1.equals(p2) : " + p1.equals(p2));
 7 }
 8 /* 輸出
 9 p1.equals(p2) : true
10 p1.equals(p2) : false
11 */
View Code

另記:String類也重寫了equals方法,實現了:只要兩個字符串長度相等及字符串中對應位置的字符相等,則兩字符串相等。能夠參考String類中的equals方法。數據結構

注意:在重寫equals方法時,方法定義要寫爲:public boolean equals(Object obj) {....} ,參數類型是:Object obj
併發

回到頂部oracle

 二. hashCode

1 public native int hashCode();
View Code

hashCode,官方解釋:返回對象的一個哈希碼,爲基於哈希表的數據結構提供便利,如:HashMap等。ide

Object類中的hashCode方法爲本地方法,在重寫hashCode方法時,需遵循如下規則:

  • 1. 在對象equals比較中使用的信息未被修改的前提下,在Java程序運行期間,同一個對象屢次調用(不一樣時間)應該始終返回同一個整數。在程序不一樣的運行期間,返回的整數不須要保持一致;
  • 2. 若是兩個對象調用 equals(Object obj) 時相等,那麼這兩個對象在調用 hashCode 時應該返回同一個整數值;
  • 3. 並不要求爲調用 equals(Object obj) 時不相等的兩個對象,返回兩個不一樣的哈希碼。只不過爲不相等的對象返回不一樣的哈希碼能夠提升哈希表的性能;

關於第2條規則,咱們繼續Point類這個例子。首先,在未重寫hashCode方法的狀況下,咱們測試兩個對象的hashCode()輸出值:

 1 public static void main(String[] args) {
 2     Point p1 = new Point(9483, 89382);
 3     Point p2 = new Point(9483, 89382);
 4     System.out.println("p1.hashCode() : " + p1.hashCode());
 5     System.out.println("p2.hashCode() : " + p2.hashCode());
 6 }
 7 /* 輸出:
 8 p1.hashCode() : 166239592
 9 p2.hashCode() : 991505714
10 */
View Code

能夠看到,在咱們定義的equals方法下相等的兩個對象,獲得的hashCode是不一樣的,如此不一致會形成什麼後果呢?咱們知道 HashMap 在存儲<Key, Value>時,若是Key1等於Key2,那麼存儲的鍵值對爲:<Key1, Value2>,即:只會存儲一個Key,使用的是最新的Value。而 HashMap 中在判斷 Key1是否等於Key2時,就使用到了它們的hashCode。在未重寫hashCode方法的狀況下,看以下測試:

 1 public static void main(String[] args) {
 2     Point p1 = new Point(9483, 89382);
 3     Point p2 = new Point(9483, 89382);
 4     
 5     HashMap<Point, Integer> map = new HashMap<Point, Integer>();
 6     map.put(p1, p1.hashCode());
 7     map.put(p2, p2.hashCode());
 8     for (Map.Entry<Point, Integer> m : map.entrySet()) {
 9         System.out.println(m);
10     }
11 }
12 /* 輸出
13 Point@9e89d68=166239592
14 Point@3b192d32=991505714
15 */
View Code

根據咱們對Point類相等的定義,p1與p2相等,而在 HashMap 中卻存入了兩個鍵值對,顯然不符合咱們的意圖。(equals與hashCode的不一致,會形成使用時產生歧義,從而致使意想不到的錯誤。因此,咱們在重寫equals方法後,也要重寫hashCode方法,使其意義一致)如今咱們來重寫hashCode方法,再進行如上測試:

1 @Override
2 public int hashCode() {
3     return (x & y) | (x ^ y);
4 }
5 /* 輸出
6 Point@17d2f=97583
7 */
View Code

根據咱們對hashCode方法的定義,對象的hashCode只與(x, y)相關,因此 p1.hashCode() == p2.hashCode() 爲 true。這樣一來,HashMap 中只會存入一個鍵值對,符合咱們的預期。

回到頂部

 三. toString

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

源碼中直接返回:對象類型名@對象hashCode的十六進制,舉個例子:

1 public static void main(String[] args) {
2     Point p1 = new Point(9483, 89382);
3     System.out.println(p1.toString());
4 }
5 /* 輸出
6 Point@17d2f
7 */
View Code

不少狀況下,咱們都要重寫toString()方法,就好比Point類,咱們想知道的是點的橫縱座標(x, y),而不是 Point@17d2f 這串不知所云的字符。

1 @Override
2 public String toString() {
3     return "(" + x + ", " + y + ")";
4 }
5 /* 輸出
6 (9483, 89382)
7 */
View Code

回到頂部

四.Clone

1 protected native Object clone() throws CloneNotSupportedException;
View Code

從方法定義入手:

  • 1. protected,代表只有本類及其子類可使用該方法來克隆對象,子類能夠重寫該方法;
  • 2. native,代表Object類中clone是本地方法;
  • 3. CloneNotSupportedException,若是調用clone方法的對象的類沒有 implememnts Cloneable,就會拋出這個異常;

如今,咱們對以前的Point類進行部分修改,爲了節省空間,我只貼出修改部分的代碼:

首先,定義Data類,用來記錄一個點所包含的相關信息;

1 public class Data {
2     int weight;
3     String name;
4     
5     Data(int weight, String name) {
6         this.weight = weight;
7         this.name = name;
8     }
9 }
View Code

而後,Point類實現Cloneable接口,而且Point類中包含一個Data類型字段,以下:

 1 public class Point implements Cloneable {
 2     int x;
 3     int y;
 4     Data data;
 5 
 6     Point(int x, int y, Data data) {
 7         this.x = x;
 8         this.y = y;
 9         this.data = data;
10     }
11     ...
12 }
View Code

測試:

 1 public static void main(String[] args) throws Exception {
 2     Data data = new Data(20, "A");
 3     Point p1 = new Point(1, 2, data);
 4     Point p2 = (Point)p1.clone();
 5     
 6     System.out.println("p1 == p2 : " + (p1 == p2));
 7     System.out.println("p1.(x, y) = " + p1.toString() + ", p2.(x, y) = " + p2.toString());
 8     System.out.println("p1.data == p2.data : " + (p1.data == p2.data));
 9 }
10 /* 輸出
11 p1 == p2 : false
12 p1.(x, y) = (1, 2), p2.(x, y) = (1, 2)
13 p1.data == p2.data : true
14 */
View Code

對於測試的輸出,咱們能夠發現:

  • 1. p1 == p2 爲 false,說明 p1.clone() 從新生成了一個對象;
  • 2. p1.(x, y) 等於 p2.(x, y),說明 p1.clone() 會把原對象的基礎數據類型字段的值拷貝給生成的新對象的對應字段;
  • 3. p1.data == p2.data 爲 true,說明引用類型字段,新對象的字段與原對象的字段引用同一個對象;

對於第3條,即Object類的clone方法是淺拷貝,理解如圖:

 

在一些併發編程情景下,咱們經常須要操做 不可變對象 來保證併發安全性。不可變對象,顧名思義就是你建立的對象不會改變,你能夠理解爲:

  • 1. 對象自己就是不可變的,如:字段都是final修飾等;
  • 2. 對象自己可變,可是咱們確保在使用的時候不會去改變它,即人爲的不可變;

(更詳細的內容,能夠參考《Java併發編程實戰》)

如今,假如我要在併發環境下使用p1.clone()出來的對象p2,並要求p2是不可變的。而事實上,其餘線程能夠經過 p1.data 來改變 p2.data 的狀態,以破壞p2的不可變性。

要想使p2不可變,咱們就須要對Point類進行深拷貝,即:對Piont類中的Data類型字段也建立一個新的對象,使得 p1.data != p2.data,以下:

 1 public class Data {
 2     ...
 3     // 自定義的clone(),並不是重寫Object類中的clone()
 4     public Data clone() {
 5         return new Data(weight, name);
 6     }
 7 }
 8 public class Point implements Cloneable {
 9     ...
10     @Override
11     protected Object clone() throws CloneNotSupportedException {
12         Point p = (Point)super.clone();
13         p.data = data.clone(); // 這裏的data.clone()與Object類中的clone()無關
14         return p;
15     }
16     ...
17 }
18 /* 重複上面的測試,輸出:
19 p1 == p2 : false
20 p1.(x, y) = (1, 2), p2.(x, y) = (1, 2)
21 p1.data == p2.data : false
22 */
View Code

思考:若是一個類中一直嵌套着包含引用類型字段,那麼咱們該怎麼才能作到深拷貝呢?很明顯,對於類中每個引用類型對象都作深拷貝。(遞歸處理)

回到頂部

 五. getClass

1 public final native Class<?> getClass();
View Code

getClass方法,返回對象的類對象,在反射中常用,例如:

Data類中有個私有方法 printInfo(),該方法在Point類中沒法正常調用,可是咱們能夠經過反射機制來調用該方法。

 1 public class Data {
 2     ...
 3     private void printInfo() {
 4         System.out.println("weight = " + weight);
 5         System.out.println("name : " + name);
 6     }
 7 }
 8 // 在Point類中
 9 public static void main(String[] args) throws Exception {
10     Data data = new Data(20, "A");
11     Class<?> clz = data.getClass();
12     Method m = clz.getDeclaredMethod("printInfo");
13     m.setAccessible(true); // 抑制Java的訪問控制檢查
14     m.invoke(data);
15 }
16 /* 輸出
17 weight = 20
18 name : A
19 */
View Code

這裏只是簡單提一下,更多關於反射的知識,會在後期總結。

回到頂部


2018-10-06

今天更新:wait系列、notify系列、finalize。

(我儘可能以簡單清晰的方式來展示個人內容,對於涉及到的知識點,這裏只是拋磚引玉,若想深刻研究,你們能夠進一步去查閱資料)

六. wait、notify

來看字面意思的解釋:

  • wait
    • wait():使當前線程等待,直到被喚醒(notified)或被中斷(interrupted),等價於wait(0L, 0) ;
    • wait(long timeout):使當前線程等待,直到被喚醒或被中斷,或者一段肯定的時間(timeout)過去了;
    • wait(long timeout, int nanos):使當前線程等待,直到被喚醒或被中斷,或者一段肯定的時間(1000000*timeout+nanos)過去了;
  • notify
    • notify():喚醒一個正在等待該對象鎖的線程,若是有多個線程在等待資源,那麼以某種規則(不肯定的)選取其中一個線程喚醒;
    • notifyAll():喚醒全部正在等待該對象鎖的線程;

(注:線程被喚醒後,須要等待當前線程釋放鎖資源,而後與其餘請求該鎖的線程競爭,獲取鎖後才能獲得執行)

字面解釋就這些,下面要寫的是我在閱讀API(JavaSE 10 & JDK10)中該部份內容時的困惑及解答。先來看看這些方法的源碼吧:

 1 /************************** wait **************************/
 2 public final void wait() throws InterruptedException {
 3     wait(0L); // 調用 wait(long timeout)
 4 }
 5 
 6 public final native void wait(long timeout) throws InterruptedException;
 7 
 8 public final void wait(long timeout, int nanos) throws InterruptedException {
 9     if (timeout < 0) {
10         throw new IllegalArgumentException("timeout value is negative");
11     }
12     if (nanos < 0 || nanos > 999999) {
13         throw new IllegalArgumentException(
14                             "nanosecond timeout value out of range");
15     }
16     /* 
17     timeout 是ms(毫秒)計時
18     nanos   是ns(納秒)計時
19     本來我覺得某些領域須要更精確的時間控制,因此才提供wait(long timeout, int nanos)這個方法
20     而當我看到源碼的時候,這不就多了個timeout++嗎?這個判斷和加法在外部也能夠作啊。
21     因此,爲何要有這個方法呢? - -?(這個問題這裏不深刻討論)
22     */
23     if (nanos > 0) {
24         timeout++;
25     }
26     wait(timeout); // 調用 wait(long timeout)
27 }
28 
29 /************************** notify ************************/
30 @HotSpotIntrinsicCandidate
31 public final native void notify();
32 
33 @HotSpotIntrinsicCandidate
34 public final native void notifyAll();
View Code

在API中,能夠看到wait的3個方法中都拋出如下異常:

  • IllegalMonitorStateException - if the current thread is not the owner of the object's monitor
  • InterruptedException - if any thread interrupted the current thread before or while the current thread was waiting. The interrupted status of the current thread is cleared when this exception is thrown.

1. IllegalMonitorStateException

若是當前線程不是該對象鎖的持有者時,拋出該異常。如何理解呢,看下面的代碼:

 1 class T1 extends Thread {
 2     
 3     @Override
 4     public void run() {
 5         try {
 6             this.wait(1000);
 7             System.out.println("wait over");
 8         } catch (InterruptedException e) {}
 9     }
10 }
11 
12 public class IllegalMonitorStateTest {
13 
14     public static void main(String[] args) {
15         T1 t = new T1();
16         t.start();
17     }
18 }
19 
20 /* 異常
21 Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
22     at java.base/java.lang.Object.wait(Native Method)
23     at T1.run(IllegalMonitorStateTest.java:6)
24 */
View Code

拋出異常的緣由:[線程 t]  執行 this.wait(),但其並未獲取 this鎖。wait操做是要釋放當前鎖資源的,都沒有獲取如何釋放呢?

官方給出的說明:在如下3種狀況下,線程爲對象鎖的持有者:

  • 執行該對象的同步實例方法,如:public synchronzied void foo(){}
  • 執行以該對象爲鎖的同步代碼塊,如:synchronized (this) {}
  • 對於類類型的對象,執行該類的靜態同步方法,如:public static synchronized void foo(){}

 因此,針對上面的例子,作如下修改(只貼出修改的部分),確保其不會拋出IllegalMonitorStateException異常。

 1 @Override
 2 public void run() {
 3     synchronized (this) {
 4         try {
 5             this.wait(1000);
 6             System.out.println("wait over");
 7         } catch (InterruptedException e) {}
 8     }
 9 }
10 /* 再進行測試,輸出
11 wait over
12 */
View Code

2. InterruptedException

 1 class T2 extends Thread {
 2     T2(String name) {
 3         super(name);
 4     }
 5     
 6     @Override
 7     public void run() {
 8         synchronized (this) {
 9             try {
10                 this.wait();
11                 System.out.println("wait over");
12             } catch (InterruptedException e) {
13                 e.printStackTrace();
14             }
15         }
16     }
17 }
18 
19 public class InterruptedExceptionTest {
20     public static void main(String[] args) throws Exception {
21         T2 t = new T2("[線程 t]");
22         t.start();
23         System.out.println(t.getName() + "中斷狀態:" + t.isInterrupted());
24         System.out.println("[線程 main]執行 t.interrupt();");
25         t.interrupt();
26         System.out.println(t.getName() + "中斷狀態:" + t.isInterrupted());
27         
28         System.out.println("------------------------------------------");
29         Thread mainThread = Thread.currentThread();
30         System.out.println("[線程 main]中斷狀態:" + mainThread.isInterrupted());
31         System.out.println("[線程 main]執行 mainThread.interrupt();");
32         mainThread.interrupt();
33         System.out.println("[線程 main]中斷狀態:" + mainThread.isInterrupted());
34         System.out.println("[線程 main]running......");
35     }
36 }
37 
38 /* 輸出 & 異常,兩個線程都有信息要輸出到控制檯,因此也可能異常信息先輸出
39 [線程 t]中斷狀態:false
40 [線程 main]執行 t.interrupt();
41 [線程 t]中斷狀態:true
42 ------------------------------------------
43 [線程 main]中斷狀態:false
44 [線程 main]執行 mainThread.interrupt();
45 [線程 main]中斷狀態:true
46 [線程 main]running......
47 java.lang.InterruptedException
48     at java.base/java.lang.Object.wait(Native Method)
49     at java.base/java.lang.Object.wait(Unknown Source)
50     at T3.run(InterruptedExceptionTest.java:10)
51 */
View Code

在當前線程等待前或等待期間,若是有其餘線程中斷當前線程,則拋出該異常。看下面的例子:

在這個示例中,咱們須要注意兩點:

  • 拋出了InterruptedException異常,由於 [線程 t] 在等待期間,[線程 main] 執行 t.interrupte()使其中斷
  • interrupt()不能中斷運行中的線程,它只能改變中斷狀態。在分割線後,能夠看到正在運行的 [線程 main] 執行了 mainThread.interrupt(),中斷狀態也由 false -> true,可是後面的輸出語句仍然被執行了,即 [線程 main] 並無被終止。那要如何才能終止呢?其實能夠經過加個判斷和return語句來完成終止,如:if (mainThread.isInterrupted()) return;

3. Note that only the locks on this object are relinquished; any other objects on which the current thread may be synchronized remain locked while the thread waits.

只有基於這個對象的鎖被釋放;在線程等待期間,當前線程可能被同步的任何其餘對象都將保持鎖定。(翻譯看不懂?直接看代碼吧)

 1 class T3 extends Thread {
 2     Object obj;
 3     
 4     T3(String name, Object obj) {
 5         super(name);
 6         this.obj = obj;
 7     }
 8     
 9     @Override
10     public void run() {
11         synchronized (this) {
12             System.out.println(Thread.currentThread().getName() + "獲取this鎖");
13             synchronized (obj) {
14                 System.out.println(Thread.currentThread().getName() + "獲取obj鎖");
15                 try {
16                     this.wait();
17                 } catch (InterruptedException e) {}
18             }
19         }
20     }
21 }
22 
23 public class OtherLockBeRetainedTest {
24     
25     public static void main(String[] args) throws Exception {
26         Object obj = "x";
27         T3 t1 = new T3("[線程 t1]", obj);
28         T3 t2 = new T3("[線程 t2]", obj);
29         t1.start();
30         Thread.sleep(2000);
31         t2.start();
32     }
33 }
34 
35 /* 輸出 (程序死鎖)
36 [線程 t1]獲取this鎖
37 [線程 t1]獲取obj鎖
38 [線程 t2]獲取this鎖
39 */
View Code

順着代碼理一下程序執行過程:

  • 首先,[線程 t1] 獲得執行,而且相繼拿到了 this鎖 和 obj鎖;
  • [線程 t1] 執行 this.wait()(等待被喚醒),把 this鎖釋放了,而 obj鎖依舊保留在 [線程 t1]手中
  • [線程 t2] 獲得執行,拿到了 this鎖,進一步須要獲取 obj鎖,發現 obj鎖依舊拽在 [線程 t1]手中,因而等待,產生了死鎖;

4. 虛假喚醒(spurious wakeups)

(我這裏只給出一種簡單的虛假喚醒狀況,更詳細的內容,你們能夠自行查閱資料,能夠參看:《Java併發編程實戰》14.2 「使用條件隊列」,《Effective Java》第69條 「併發工具優先於wait和notify」)

在某些狀況下,咱們須要某個條件成立,線程才能往下執行,而下面的示例中,未必按這個套路出牌:

 1 class T4 extends Thread {
 2    boolean condition;
 3    
 4    T4(boolean condition) {
 5        this.condition = condition;
 6    }
 7    
 8    @Override
 9    public void run() {
10        synchronized (this) {
11            try {
12                if (!condition) {
13                    this.wait();
14                }
15                // 當 condition == true 時,執行下面的操做
16                System.out.println("condition : " + condition);
17            } catch (InterruptedException e) {}
18        }
19    }
20 }
21 
22 public class SpuriousWakeupTest {
23     public static void main(String[] args) throws Exception {
24         T4 t = new T4(false);
25         t.start();
26         Thread.sleep(1000);
27         synchronized (t) {
28             t.notify();
29         }
30     }
31 }
32 
33 /* 輸出
34 condition : false
35 */
View Code

上面的例子,咱們的原意是:當condition爲true時,[線程 t] 繼續執行下面的操做,不然繼續等待直到條件成立。固然,[線程 t] 第一次判斷condition時,符合咱們的意圖,進行了等待;後來被主線程notify喚醒,condition依舊爲false,而 [線程 t] 卻執行了後續的操做,顯然不符合咱們的意圖。雖然 [線程 t] 真的被喚醒了,可是在咱們的業務邏輯定義下,它不該該被喚醒執行操做,應該繼續等待。對於這樣的喚醒,咱們稱爲虛假喚醒(狀況不止於此)。如何解決這個問題呢?其實只須要作一點小修改便可,即:把 if語句換成 while語句,以下:

 1 @Override
 2 public void run() {
 3     synchronized (this) {
 4         try {
 5             while (!condition) {
 6                 this.wait();
 7             }
 8             // 當 condition == true 時,執行下面的操做
 9             System.out.println("condition : " + condition);
10         } catch (InterruptedException e) {}
11     }
12 }
View Code

回到頂部

七. finalize

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

finalize,終結方法,當GC認爲對象再也不被引用時,會先調用finalize(),再回收該對象。子類能夠重寫finalize方法,來處理一些系統資源或者完成其餘清理工做。(自Java 9 開始被棄用)

關於finalize,有如下4個點須要注意(摘自《Effective Java》第2版,第7條:避免使用終結方法):

  • Java語言規範不只不保證終結方法會被及時地執行,並且根本就不保證它們會被執行;
  • System.gcSystem.runFinalization這兩個方法確實增長了終結方法被執行的機會,但它們一樣不保證終結方法必定會被執行;
  • 使用終結方法存在很是嚴重的性能損失,使用不當可能致使內存溢出(Java的Finalizer引起的內存溢出);
  • 終結方法鏈」並不會被自動執行。若是類(非Object)有終結方法,而且子類覆蓋了終結方法,子類的終結方法就必須手工調用超類的終結方法;

在我看來,咱們能夠把finalize()當作是對象被GC回收前的回調,來看個「對象復活」的例子。代碼來自:http://www.javashuo.com/article/p-htdktnss-ht.html

 1 public class FinalizeTest {
 2 
 3     public static FinalizeTest SAVE_HOOK = null;
 4 
 5     public static void main(String[] args) throws Exception {
 6         SAVE_HOOK = new FinalizeTest();
 7         SAVE_HOOK = null;
 8         System.gc();
 9         Thread.sleep(500);
10         if (null != SAVE_HOOK) {
11             System.out.println("Yes, I am still alive");
12         } else {
13             System.out.println("No, I am dead");
14         }
15         SAVE_HOOK = null;
16         System.gc();
17         Thread.sleep(500);
18         if (null != SAVE_HOOK) {
19             System.out.println("Yes, I am still alive");
20         } else {
21             System.out.println("No, I am dead");
22         }
23     }
24   
25     @Override
26     protected void finalize() throws Throwable {
27         super.finalize();
28         System.out.println("execute method finalize()");
29         SAVE_HOOK = this;
30     }
31 }
32 
33 /* 輸出
34 execute method finalize()
35 Yes, I am still alive
36 No, I am dead
37 */
View Code

分析(爲了描述方便,把 new FinalizeTest() 生成的對象叫作 對象A):

  • 第1次調用System.gc(),此時SAVE_HOOK = null,沒有引用指向對象A。GC認爲對象A能夠回收,而該類重寫了finalize方法,因此會先調用對象的finalize(),所以輸出「execute method finalize()」;
  • 調用finalize()後,SAVE_HOOK = this(對象A),因此接下來的 if 判斷爲true,輸出「Yes, I am still alive」;(就這樣,原本要被回收的對象復活了
  • 第2次調用System.gc(),此時SAVE_HOOK = null,GC認爲對象A能夠回收,可是不會再去調用該對象的finalize()(GC只會調用一次對象的finalize方法)。因此接下來的 if 判斷爲false,輸出"No, I am dead";

總的來講,終結方法(finalizer)一般是不可預測的,也是很危險的,通常狀況下是沒必要要的。使用終結方法會致使行爲不穩定、下降性能,以及可移植性問題

那麼,若是類的對象中封裝了資源(如文件或線程)確實須要終止,怎麼作才能不用編寫終結方法呢?

其實,咱們只須要提供一個顯示的終止方法便可,比較典型的例子:InputStream、OutputStream和java.sql.Connection上的close方法。一般,顯示的終止方法會與try-finally結構結合使用,以確保及時終止。代碼結構以下:

1 FOO foo = new Foo();
2 try {
3     // do something
4 } finally {
5     foo.terminate(); // 如flie.close();
6 }
View Code

終結方法有什麼好處呢?或者說,咱們何時該使用它?

  • 當對象的全部者忘記調用顯示的終止方法時,終結方法能夠充當「安全網」。遲一點釋放關鍵資源總比永遠不釋放要好,FileInputStream、FileOutputStream等都有終結方法;
  • 清理本地對等體(native peer)。本地對等體是一個本地對象(native object),普通對象經過本地方法(native method)委託給一個本地對象由於本地對等體不是一個普通對象,因此GC不會知道它,當它的Java對等體被回收的時候,它不會被回收

由於對JVM和GC方面不太瞭解,因此在深刻理解和實踐時比較費勁,Emmmmm....再接再礪吧!

回到頂部

轉載請說明出處,have a good time! :D

相關文章
相關標籤/搜索