摘要:垃圾回收機制是守護執行緒的最佳示例,因為它始終在後臺執行。

本文分享自華為雲社群《一文帶你瞭解Java 中的垃圾回收機制》,作者:海擁。

介紹

  • 在 C/C++ 中,程式設計師負責物件的建立和銷燬。通常程式設計師會忽略無用物件的銷燬。由於這種疏忽,在某些時候,為了建立新物件,可能沒有足夠的記憶體可用,整個程式將異常終止,導致OutOfMemoryErrors
  • 但是在 Java 中,程式設計師不需要關心所有不再使用的物件。垃圾回收機制自動銷燬這些物件。
  • 垃圾回收機制是守護執行緒的最佳示例,因為它始終在後臺執行。
  • 垃圾回收機制的主要目標是通過銷燬無法訪問的物件來釋放堆記憶體。

重要條款:

  • 無法訪問的物件: 如果一個物件不包含對它的任何引用,則稱其為無法訪問的物件。另請注意,屬於隔離島的物件也無法訪問。
Integer i = new Integer(4);
// 新的 Integer 物件可通過 'i' 中的引用訪問
i = null;
// Integer 物件不再可用。

  • 垃圾回收的資格: 如果物件無法訪問,則稱該物件有資格進行 GC(垃圾回收)。在上圖中,在i = null 之後; 堆區域中的整數物件 4 有資格進行垃圾回收。

使物件符合 GC 條件的方法

  • 即使程式設計師不負責銷燬無用的物件,但如果不再需要,強烈建議使物件不可訪問(因此有資格進行 GC)。
  • 通常有四種不同的方法可以使物件適合垃圾回收。
  1. 取消引用變數
  2. 重新分配引用變數
  3. 在方法內部建立的物件
  4. 隔離島

以上所有帶有示例的方法都在單獨的文章中討論:如何使物件符合垃圾收集條件

請求JVM執行垃圾收集器的方式

  • 一旦我們使物件符合垃圾收集條件,垃圾收集器可能不會立即銷燬它。每當 JVM 執行垃圾收集器程式時,只會銷燬物件。但是當JVM執行Garbage Collector時,我們無法預料。
  • 我們還可以請求 JVM 執行垃圾收集器。有兩種方法可以做到:
  1. 使用System.gc() 方法:系統類包含靜態方法gc() 用於請求 JVM 執行垃圾收集器。
  2. 使用Runtime.getRuntime().gc() 方法:執行時類允許應用程式與執行應用程式的 JVM 互動。因此,通過使用其 gc() 方法,我們可以請求 JVM 執行垃圾收集器。
// 演示請求 JVM 執行垃圾收集器的 Java 程式
public class Test
{
public static void main(String[] args) throws InterruptedException
{
Test t1 = new Test();
Test t2 = new Test(); // 取消引用變數
t1 = null; // 請求 JVM 來執行垃圾收集器
System.gc(); // 取消引用變數
t2 = null; // 請求 JVM 來執行垃圾收集器
Runtime.getRuntime().gc(); } @Override
// 在垃圾回收之前,在物件上呼叫一次 finalize 方法
protected void finalize() throws Throwable
{
System.out.println("垃圾收集器呼叫");
System.out.println("物件垃圾收集:" + this);
}
}

輸出:

垃圾收集器呼叫
物件垃圾收集:haiyong.Test@7ad74083
垃圾收集器呼叫
物件垃圾收集:haiyong.Test@7410a1a9

筆記 :

    1. 不能保證以上兩種方法中的任何一種都一定會執行垃圾收集器。
    2. 呼叫System.gc() 等效於呼叫:Runtime.getRuntime().gc()

定稿

  • 就在銷燬物件之前,垃圾收集器呼叫物件的finalize() 方法來執行清理活動。一旦finalize() 方法完成,垃圾收集器就會銷燬該物件。
  • finalize() 方法存在於具有以下原型的Object 類中。
protected void finalize() throws Throwable

根據我們的要求,我們可以覆蓋finalize() 方法來執行我們的清理活動,例如關閉資料庫連線。

筆記 :

  1. 垃圾收集器而不是JVM呼叫的finalize() 方法。雖然垃圾收集器是JVM的模組之一。
  2. 物件類 finalize() 方法有空實現,因此建議覆蓋finalize() 方法來處理系統資源或執行其他清理。
  3. 對於任何給定的物件,finalize() 方法永遠不會被多次呼叫。
  4. 如果finalize() 方法丟擲未捕獲的異常,則忽略該異常並終止該物件的終結。

有關finalize() 方法的示例,請參閱Java 程式的輸出第十套之垃圾收集

讓我們舉一個真實的例子,在那裡我們使用垃圾收集器的概念。

假設你去位元組跳動實習,他們告訴你寫一個程式,計算在公司工作的員工人數(不包括實習生)。要製作這個程式,你必須使用垃圾收集器的概念。

這是您在公司獲得的實際任務:-

問: 編寫一個程式來建立一個名為 Employee 的類,該類具有以下資料成員。
1.一個ID,用於儲存分配給每個員工的唯一ID。
2.員工姓名。
3.員工年齡。

另外,提供以下方法-

  1. 用於初始化名稱和年齡的引數化建構函式。ID 應在此建構函式中初始化。
  2. 顯示 ID、姓名和年齡的方法 show()。
  3. 顯示下一個員工的 ID 的方法 showNextId()。

現在對垃圾回收機制不瞭解的初學者可能會這樣編寫程式碼:

//計算在公司工作的員工人數的程式

class Employee
{
private int ID;
private String name;
private int age;
private static int nextId=1;
//它是靜態的,因為它在所有物件之間保持通用並由所有物件共享
public Employee(String name,int age)
{
this.name = name;
this.age = age;
this.ID = nextId++;
}
public void show()
{
System.out.println
("Id="+ID+"\nName="+name+"\nAge="+age);
}
public void showNextId()
{
System.out.println
("Next employee id will be="+nextId);
}
}
class UseEmployee
{
public static void main(String []args)
{
Employee E=new Employee("GFG1",33);
Employee F=new Employee("GFG2",45);
Employee G=new Employee("GFG3",25);
E.show();
F.show();
G.show();
E.showNextId();
F.showNextId();
G.showNextId(); { //這是保留所有實習生的子塊。
Employee X=new Employee("GFG4",23);
Employee Y=new Employee("GFG5",21);
X.show();
Y.show();
X.showNextId();
Y.showNextId();
}
//這個大括號之後,X 和 Y 將被移除。因此現在它應該顯示 nextId 為 4。
E.showNextId();//這一行的輸出應該是 4,但它會給出 6 作為輸出。
}
}

現在獲得正確的輸出:

現在垃圾收集器(gc)將看到 2 個空閒的物件。現在遞減 nextId,gc(garbage collector) 只會在我們的程式設計師在我們的類中覆蓋它時呼叫方法 finalize() 。如前所述,我們必須請求 gc(garbage collector),為此,我們必須在關閉子塊的大括號之前編寫以下 3 個步驟。

  1. 將引用設定為 null(即 X = Y = null;)
  2. 呼叫,System.gc();
  3. 呼叫,System.runFinalization();

現在計算員工人數的正確程式碼(不包括實習生)

// 計算不包括實習生的員工人數的正確程式碼
class Employee
{
private int ID;
private String name;
private int age;
private static int nextId=1;
//它是靜態的,因為它在所有物件之間保持通用並由所有物件共享
public Employee(String name,int age)
{
this.name = name;
this.age = age;
this.ID = nextId++;
}
public void show()
{
System.out.println
("Id="+ID+"\nName="+name+"\nAge="+age);
}
public void showNextId()
{
System.out.println
("Next employee id will be="+nextId);
}
protected void finalize()
{
--nextId;
//在這種情況下,gc 會為 2 個物件呼叫 finalize() 兩次。
}
} // 它是 Employee 類的右括號
class UseEmployee
{
public static void main(String []args)
{
Employee E=new Employee("GFG1",33);
Employee F=new Employee("GFG2",45);
Employee G=new Employee("GFG3",25);
E.show();
F.show();
G.show();
E.showNextId();
F.showNextId();
G.showNextId(); {
//這是保留所有實習生的子塊。
Employee X=new Employee("GFG4",23);
Employee Y=new Employee("GFG5",21);
X.show();
Y.show();
X.showNextId();
Y.showNextId();
X = Y = null;
System.gc();
System.runFinalization();
}
E.showNextId();
}
}

點選關注,第一時間瞭解華為雲新鮮技術~