Java垃圾回收是如何工作的?

edithfang發表於2014-10-30
目錄

1.垃圾回收介紹
2.垃圾回收是如何工作的?
3.垃圾回收的類別
4.垃圾回收監視和分析
       
本教程是為了理解基本的Java垃圾回收以及它是如何工作的。這是垃圾回收教程系列的第二部分。希望你已經讀過了第一部分:《Java 垃圾回收介紹》。
       
Java 垃圾回收是一項自動化的過程,用來管理程式所使用的執行時記憶體。通過這一自動化過程,JVM 解除了程式設計師在程式中分配和釋放記憶體資源的開銷。

啟動Java垃圾回收
      
 作為一個自動的過程,程式設計師不需要在程式碼中顯示地啟動垃圾回收過程。System.gc()和Runtime.gc()用來請求JVM啟動垃圾回收。
       
雖然這個請求機制提供給程式設計師一個啟動 GC 過程的機會,但是啟動由 JVM負責。JVM可以拒絕這個請求,所以並不保證這些呼叫都將執行垃圾回收。啟動時機的選擇由JVM決定,並且取決於堆記憶體中Eden區是否可用。JVM將這個選擇留給了Java規範的實現,不同實現具體使用的演算法不盡相同。
       
毋庸置疑,我們知道垃圾回收過程是不能被強制執行的。我剛剛發現了一個呼叫System.gc()有意義的場景。通過這篇文章瞭解一下適合呼叫System.gc() 這種極端情況。

Java垃圾回收過程
       
垃圾回收是一種回收無用記憶體空間並使其對未來例項可用的過程。



 

Eden 區:當一個例項被建立了,首先會被儲存在堆記憶體年輕代的 Eden 區中。

注意:如果你不能理解這些詞彙,我建議你閱讀這篇 垃圾回收介紹 ,這篇教程詳細地介紹了記憶體模型、JVM 架構以及這些術語。

Survivor 區(S0 和 S1):作為年輕代 GC(Minor GC)週期的一部分,存活的物件(仍然被引用的)從 Eden 區被移動到 Survivor 區的 S0 中。

類似的,垃圾回收器會掃描 S0 然後將存活的例項移動到 S1 中。

(譯註:此處不應該是Eden和S0中存活的都移到S1麼,為什麼會先移到S0再從S0移到S1?)
       
死亡的例項(不再被引用)被標記為垃圾回收。根據垃圾回收器(有四種常用的垃圾回收器,將在下一教程中介紹它們)選擇的不同,要麼被標記的例項都會不停地從記憶體中移除,要麼回收過程會在一個單獨的程式中完成。

老年代: 老年代(Old or tenured generation)是堆記憶體中的第二塊邏輯區。當垃圾回收器執行 Minor GC 週期時,在 S1 Survivor 區中的存活例項將會被晉升到老年代,而未被引用的物件被標記為回收。

老年代 GC(Major GC):相對於 Java 垃圾回收過程,老年代是例項生命週期的最後階段。Major GC 掃描老年代的垃圾回收過程。如果例項不再被引用,那麼它們會被標記為回收,否則它們會繼續留在老年代中。

記憶體碎片:一旦例項從堆記憶體中被刪除,其位置就會變空並且可用於未來例項的分配。這些空出的空間將會使整個記憶體區域碎片化。為了例項的快速分配,需要進行碎片整理。基於垃圾回收器的不同選擇,回收的記憶體區域要麼被不停地被整理,要麼在一個單獨的GC程式中完成。

垃圾回收中例項的終結
       
在釋放一個例項和回收記憶體空間之前,Java 垃圾回收器會呼叫例項各自的 finalize() 方法,從而該例項有機會釋放所持有的資源。雖然可以保證 finalize() 會在回收記憶體空間之前被呼叫,但是沒有指定的順序和時間。多個例項間的順序是無法被預知,甚至可能會並行發生。程式不應該預先調整例項之間的順序並使用 finalize() 方法回收資源。

任何在 finalize過程中未被捕獲的異常會自動被忽略,然後該例項的 finalize 過程被取消。

JVM 規範中並沒有討論關於弱引用的垃圾回收機制,也沒有很明確的要求。具體的實現都由實現方決定。

垃圾回收是由一個守護執行緒完成的。

物件什麼時候符合垃圾回收的條件?

所有例項都沒有活動執行緒訪問。

沒有被其他任何例項訪問的迴圈引用例項。 
       
Java 中有不同的引用型別。判斷例項是否符合垃圾收集的條件都依賴於它的引用型別。



 

在編譯過程中作為一種優化技術,Java 編譯器能選擇給例項賦 null 值,從而標記例項為可回收。
class Animal {
public static void main(String[] args) {
Animal lion = new Animal();
System.out.println("Main is completed.");
}
protected void finalize() {
System.out.println("Rest in Peace!");
}
}
在上面的類中,lion 物件在例項化行後從未被使用過。因此 Java 編譯器作為一種優化措施可以直接在例項化行後賦值lion = null。因此,即使在 SOP 輸出之前, finalize 函式也能夠列印出 'Rest in Peace!'。我們不能證明這確定會發生,因為它依賴JVM的實現方式和執行時使用的記憶體。然而,我們還能學習到一點:如果編譯器看到該例項在未來再也不會被引用,能夠選擇並提早釋放例項空間。
       
關於物件什麼時候符合垃圾回收有一個更好的例子。例項的所有屬效能被儲存在暫存器中,隨後暫存器將被訪問並讀取內容。無一例外,這些值將被寫回到例項中。雖然這些值在將來能被使用,這個例項仍然能被標記為符合垃圾回收。這是一個很經典的例子,不是嗎?
       
當被賦值為null時,這是很簡單的一個符合垃圾回收的示例。當然,複雜的情況可以像上面的幾點。這是由 JVM 實現者所做的選擇。目的是留下儘可能小的記憶體佔用,加快響應速度,提高吞吐量。為了實現這一目標, JVM 的實現者可以選擇一個更好的方案或演算法在垃圾回收過程中回收記憶體空間。
       
當 finalize() 方法被呼叫時,JVM 會釋放該執行緒上的所有同步鎖。

GC Scope 示例程式
Class GCScope {
       GCScope t;
       static int i = 1;
       public static void main(String args[]) {
              GCScope t1 = new GCScope();
              GCScope t2 = new GCScope();
              GCScope t3 = new GCScope();
              // No Object Is Eligible for GC
              t1.t = t2; // No Object Is Eligible for GC
              t2.t = t3; // No Object Is Eligible for GC
              t3.t = t1; // No Object Is Eligible for GC
              t1 = null; // No Object Is Eligible for GC (t3.t still has a reference to t1)
              t2 = null; // No Object Is Eligible for GC (t3.t.t still has a reference to t2)
              t3 = null; // All the 3 Object Is Eligible for GC (None of them have a reference.
              // only the variable t of the objects are referring each other in a
              // rounded fashion forming the Island of objects with out any external
              // reference)
       }
       protected void finalize() {
              System.out.println("Garbage collected from object" + i);
              i++;
       }
class GCScope {
       GCScope t;
       static int i = 1;
       public static void main(String args[]) {
              GCScope t1 = new GCScope();
              GCScope t2 = new GCScope();
              GCScope t3 = new GCScope();
              // 沒有物件符合GC
              t1.t = t2; // 沒有物件符合GC
              t2.t = t3; // 沒有物件符合GC
              t3.t = t1; // 沒有物件符合GC
              t1 = null; // 沒有物件符合GC (t3.t 仍然有一個到 t1 的引用)
              t2 = null; // 沒有物件符合GC (t3.t.t 仍然有一個到 t2 的引用)
              t3 = null; // 所有三個物件都符合GC (它們中沒有一個擁有引用。
              // 只有各物件的變數 t 還指向了彼此,
              // 形成了一個由物件組成的環形的島,而沒有任何外部的引用。)
       }
       protected void finalize() {
              System.out.println("Garbage collected from object" + i);
              i++;
       }
GC OutOfMemoryError 的示例程式
       
GC並不保證記憶體溢位問題的安全性,粗心寫下的程式碼會導致 OutOfMemoryError。
import java.util.LinkedList;
import java.util.List;
public class GC {
       public static void main(String[] main) {
              List l = new LinkedList();
              // Enter infinite loop which will add a String to the list: l on each
              // iteration.
              do {
                     l.add(new String("Hello, World"));
              } while (true);
       }
}
輸出:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
       at java.util.LinkedList.linkLast(LinkedList.java:142)
       at java.util.LinkedList.add(LinkedList.java:338)
       at com.javapapers.java.GCScope.main(GCScope.java:12)
       
接下來是垃圾收集系列教程的第三部分,我們將會看到常用的 不同 的Java垃圾收集器。

原文連結:javapapers

翻譯: ImportNew.com - 伍翀

譯文連結: http://www.importnew.com/13493.html
來自:PHP100
相關閱讀
評論(1)

相關文章