你好!

我想如果我告訴您您的計算機的內存量有限,您不會太驚訝 :) 即使是硬盤驅動器——通常比 RAM 存儲容量大很多倍——也可以裝滿你最喜歡的遊戲、電視節目、和更多。為防止這種情況發生,您需要監控內存的當前狀態並從計算機中刪除不需要的文件。Java 編程與這一切有什麼關係?一切!畢竟,當 Java 機器創建任何對象時,它都會為該對象分配內存。

在一個真正的大程序中,創建了數以萬計的對象,每個對像都有自己的一塊內存分配給它。但是你認為所有這些物體存在多久了?他們在我們的程序運行的整個過程中都“活著”嗎?當然不是。即使具有 Java 對象的所有優點,它們也不是不朽的 :) 對像有自己的生命週期。今天我們將暫停編寫代碼,看看這個過程 :) 此外,它對於您理解程序的工作原理和資源的管理方式非常重要。那麼,對象的生命從什麼時候開始呢?就像一個人——從它的誕生,即創造。


Cat cat = new Cat(); // Here the lifecycle of our Cat object begins!

首先,Java 虛擬機分配所需的內存量來創建對象。然後它創建對該內存的引用。在我們的例子中,該引用稱為cat,因此我們可以跟踪它。然後它的所有變量都被初始化,構造函數被調用,然後——噠噠!— 我們新造的對象過著自己的生活 :)

對象的壽命各不相同,因此我們無法在此處提供確切的數字。在任何情況下,它都會在程序中存在一段時間並執行其功能。準確地說,只要有對它的引用,一個對象就是“活著”的。一旦沒有剩餘的引用,對象就會“死亡”。例子:


public class Car {
  
   String model;

   public Car(String model) {
       this.model = model;
   }

   public static void main(String[] args) {
       Car lamborghini  = new Car("Lamborghini Diablo");
       lamborghini = null;

   }

}

Lamborghini Diablo 汽車對像在該方法的第二行停止活動main()。只有一個對它的引用,然後該引用被設置為等於null。由於沒有對 Lamborghini Diablo 的剩餘引用,分配的內存就變成了“垃圾”。不必將引用設置為 null 即可發生這種情況:


public class Car {

   String model;

   public Car(String model) {
       this.model = model;
   }

   public static void main(String[] args) {
       Car lamborghini  = new Car("Lamborghini Diablo");

       Car lamborghiniGallardo = new Car("Lamborghini Gallardo");
       lamborghini = lamborghiniGallardo;
   }

}

這裡我們創建了第二個對象,然後將這個新對象分配給引用lamborghini。現在Lamborghini Gallardo對像有兩個引用,但Lamborghini Diablo對像沒有。這意味著該Diablo對象現在是垃圾。這就是 Java 的內置機制,稱為垃圾收集器 (GC) 發揮作用的時候。

垃圾收集器是一種Java 內部機制,負責釋放內存,即從內存中刪除不需要的對象。我們在這裡選擇機器人真空吸塵器的圖片是有充分理由的。畢竟,垃圾收集器的工作方式大致相同:在後台,它圍繞您的程序“旅行”,幾乎不需要您付出任何努力即可收集垃圾。它的工作是刪除程序中不再使用的對象。

這樣做可以為其他對象釋放計算機內存。還記得我們在課程開始時說過,在日常生活中,您必須監控計算機的狀態並刪除不需要的文件嗎?好吧,對於 Java 對象,垃圾收集器會為您完成這項工作。垃圾收集器在您的程序運行時重複運行:您不必顯式調用它或給它命令,儘管這在技術上是可行的。稍後我們將更多地討論它並更詳細地分析它的工作。

當垃圾收集器到達一個對象時,就在銷毀該對象之前,它會調用finalize()該對象的一個特殊方法——。該方法可以釋放該對象使用的其他資源。該finalize()方法是類的一部分Object。這意味著除了您之前遇到的equals(),hashCode()和方法之外,每個對像都有這個方法。toString()它與其他方法的不同之處在於它——我該怎麼說呢——非常反复無常。

特別是,它並不總是在對象銷毀之前調用。編程是一項精確的工作。程序員告訴計算機做某事,計算機就會去做。我想你已經習慣了這種行為,所以一開始你可能很難接受下面這個想法:“在銷毀對象之前,調用類finalize()的方法Object。或者可能沒有調用。這完全取決於你的運氣!”

然而,這是真的。finalize()Java 機器根據具體情況自行決定是否調用該方法。例如,讓我們嘗試運行以下代碼作為實驗:


public class Cat {

   private String name;

   public Cat(String name) {
       this.name = name;
   }

   public Cat() {
   }

   public static void main(String[] args) throws Throwable {
       for (int i = 0 ; i < 1000000; i++) {
           Cat cat = new Cat();
           cat = null; // This is when the first object becomes available to the garbage collector
       }
   }

   @Override
   protected void finalize() throws Throwable {
       System.out.println("Cat object destroyed!");
   }
}

我們創建一個Cat對象,然後在下一行代碼中將其唯一引用設置為 null。我們這樣做了一百萬次。我們明確地覆蓋了該finalize()方法,以便它向控制台打印一個字符串一百萬次(每次銷毀一個Cat對象時打印一次)。但不是!準確地說,它在我的電腦上只運行了 37,346 次!也就是說,安裝在我機器上的 Java 機器每 27 次中只有一次決定調用該finalize()方法。

在其他情況下,垃圾收集在沒有它的情況下發生。嘗試自己運行這段代碼:您很可能會得到不同的結果。如您所見,finalize()很難稱為可靠的合作夥伴 :) 所以,對未來的一點建議:不要依賴finalize()釋放關鍵資源的方法。可能 JVM 會調用它,也可能不會。誰知道?

如果當您的對象處於活動狀態時,它持有一些對性能非常重要的資源,例如,一個打開的數據庫連接,最好在您的類中創建一個特殊的方法來釋放它們,然後在對像不再存在時顯式調用它需要。這樣您就可以確定您的程序的性能不會受到影響。從一開始,我們就說使用內存和清除垃圾非常重要,這是事實。不正確地處理資源和誤解如何清理不必要的對象會導致內存洩漏。這是最著名的編程錯誤之一。

如果程序員編寫的代碼不正確,每次都可能為新創建的對象分配新內存,而舊的、不需要的對象可能無法被垃圾收集器移除。由於我們用機器人真空吸塵器做了類比,想像一下如果在啟動機器人之前,您將襪子撒在房子周圍,打碎玻璃花瓶,並在地板上留下樂高積木,會發生什麼情況。當然,機器人會嘗試完成它的工作,但有時它會卡住。

為了讓機器人吸塵器正常工作,您需要保持地板狀況良好,並清除機器人無法處理的任何東西。同樣的原則也適用於 Java 的垃圾收集器。如果程序中有許多無法清理的對象(如襪子或機器人真空吸塵器的樂高積木),有時您會耗盡內存。凍結的可能不僅僅是您的程序——計算機上運行的所有其他程序都可能受到影響。他們也可能沒有足夠的內存。

你不需要記住這個。您只需要了解其工作原理。