在Java中,垃圾回收是一個高級話題。Java的GC知識有助於我們微調應用程序運行時的性能。
Java中的垃圾回收
- 在Java中,程序員不需要關心銷毀不再使用的對象。垃圾回收器會負責處理這一點。
- 垃圾回收器是一個在後台持續運行的守護線程。基本上,它通過銷毀不可達的對象來釋放堆內存。
- 不可達的對象是那些不再被程序的任何部分引用的對象。
- 我們可以通過JVM選項為我們的Java程序選擇垃圾回收器,我們將在本教程的後面部分進行介紹。
自動垃圾回收是如何工作的?
自動垃圾收集是一個過程,它查看堆內存,識別(也稱為“標記”)不可達對象,並通過壓縮摧毀它們。這種方法的問題在於,隨著對象數量的增加,垃圾收集時間不斷增加,因為它需要遍歷整個對象列表,尋找不可達對象。然而,對應用程序的實證分析顯示,大多數對象的生存時間很短。這種行為被用來改善JVM的性能,採用的方法通常稱為分代垃圾收集。在這種方法中,堆空間被劃分為不同的代,如年輕代、老年代或歸幹代和永久代。年輕代堆空間是新對象創建的地方。一旦填滿,將進行小垃圾收集(也稱為小GC)。這意味著,此代的所有死對象都被摧毀。這個過程很快,因為從圖表中可以看出,大多數對象都會死亡。年輕代中的存活對象會變老,最終移到老年代。老年代用於存儲長期存活的對象。通常,為年輕代對象設置一個閾值,當達到該年齡時,將對象移動到老年代。最終,需要收集老年代。這個事件被稱為主GC(主要垃圾收集)。通常這要慢得多,因為它涉及所有活對象。還有一個全GC,這意味著清理整個堆 – 包括年輕代和老年代空間。最後,直到Java 7,都有一個永久代(或永久代),其中包含JVM描述應用程序中使用的類和方法所需的元數據。在Java 8中將其刪除。
Java 垃圾收集器
JVM 實際上提供了四種不同的垃圾收集器,它們都是分代的。每一種都有它們自己的優點和缺點。我們可以自行選擇使用哪種垃圾收集器,而在吞吐量和應用程序暫停方面可能存在顯著差異。所有這些都將托管堆分成不同的段,使用古老的假設,即堆中的大多數對象壽命短暫,應該迅速回收。因此,這四種垃圾收集器分別是:
Serial GC
這是最簡單的垃圾收集器,適用於單線程系統和小堆大小。在運作時凍結所有應用程序。可通過使用 -XX:+UseSerialGC
JVM 選項啟用。
Parallel/Throughput GC
這是JDK 8中JVM的默認收集器。如其名所示,它使用多個線程來掃描堆空間並執行內存整理。該收集器的一個缺點是在進行小型或完全GC時會暫停應用程序線程。如果應用程序能夠處理這些暫停並優化收集器引起的CPU開銷,則此收集器最適合使用。
CMS收集器
CMS(”concurrent-mark-sweep”)收集器算法使用多線程(”concurrent”)來掃描堆(”mark”)以查找可以回收的未使用對象(”sweep”)。此收集器在兩種情況下會進入“停止-全局”(STW)模式: – 在初始化根標記時,即從線程入口點或靜態變量可訪問的舊代中可達的對象 – 當應用程序在算法並行運行時更改了堆的狀態並迫使其返回並進行一些最後的處理以確保標記了正確的對象。此收集器可能面臨晉升失敗。如果要將一些年輕代的對象移動到舊代,並且收集器沒有足夠的時間在舊代空間中創建空間,則會發生晉升失敗。為了防止這種情況,我們可以為舊代提供更多的堆大小或為收集器提供更多的後台線程。
G1 收集器
最後但並非最不重要的是 Garbage-First 收集器,設計用於大於 4GB 的堆大小。它將堆大小劃分為從 1MB 到 32MB 的區域,基於堆大小。有一個並發的全局標記階段來確定整個堆中對象的活動性。標記階段完成後,G1 知道哪些區域大部分是空的。它首先從這些區域收集不可達對象,這通常會產生大量的可用空間。因此 G1 首先收集這些區域(包含垃圾),因此得名 Garbage-First。G1 還使用暫停預測模型以滿足用戶定義的暫停時間目標。它基於指定的暫停時間目標選擇要收集的區域數量。G1 垃圾收集周期包括圖中顯示的階段:
-
僅年輕階段:此階段僅包括年輕代對象並將它們提升到老年代。從僅年輕階段轉換為空間回收階段的過渡是當老年代佔據到一定閾值時,即啟動堆佔用閾值。此時,G1 規劃進行初始標記僅年輕階段收集,而不是常規的僅年輕階段收集。
-
初始標記:這種類型的收集除了常規的僅年輕一代收集外,還開始進行標記過程。並發標記確定老年一代區域中的所有當前存活物件,以便在接下來的空間回收階段中保留它們。儘管標記尚未完全完成,常規的僅年輕一代收集仍可能發生。標記過程在兩個特殊的停頓時間中完成:重新標記和清理。
-
重新標記:這個停頓時間完成標記過程本身,並執行全局引用處理和類卸載。在重新標記和清理之間,G1同時計算活性信息的摘要,這個摘要將在清理停頓時間中最終確定並用於更新內部數據結構。
-
清理:此暫停還將完全空白的區域納入考慮,並確定是否實際進行空間回收階段。如果後續有空間回收階段,僅進行年輕代收集的階段將完成一次僅進行年輕代的收集。
-
空間回收階段:此階段包括多個混合收集 – 除了年輕代區域外,還疏散老年代區域的活動對象。當G1確定疏散更多老年代區域不值得付出足夠的空閒空間時,空間回收階段結束。
G1 可以使用 –XX:+UseG1GC
標誌啟用。這個策略減少了在背景線程完成掃描不可達對象之前堆積耗盡的機會。此外,它可以在運行時壓縮堆,而 CMS 收集器只能在 STW 模式下執行此操作。在 Java 8 中,使用 G1 收集器提供了一個美麗的優化,稱為 字符串去重。我們知道,表示字符串的字符數組佔據了大部分堆空間。一項新的優化已經完成,使得 G1 收集器能夠識別堆中重複多次的字符串並將它們修改為指向同一個內部 char[] 數組,以避免多個相同字符串不必要地存在於堆中。我們可以使用 -XX:+UseStringDeduplication
JVM 參數來啟用此優化。G1 是 JDK 9 中的默認垃圾收集器。
Java 8 的 PermGen 和 Metaspace
如前所述,自 Java 8 起已移除永久代空间。因此,JDK 8 HotSpot JVM 現在使用本機內存來表示類元數據,稱為 Metaspace。大多數類元數據的分配都是使用本機內存進行的。此外,還有一個新的標誌 MaxMetaspaceSize,用於限制用於類元數據的內存量。如果我們未指定此值,則 Metaspace 會根據運行應用程序的需求在運行時調整大小。當類元數據使用量達到 MaxMetaspaceSize 限制時,將觸發 Metaspace 垃圾回收。過度的 Metaspace 垃圾回收可能是類、類加載器內存泄漏或應用程序大小不足的症狀。這就是 Java 中垃圾回收的全部內容。我希望你已經了解了我們在 Java 中擁有的不同垃圾回收器。參考資料:Oracle 文檔,G1 GC。
Source:
https://www.digitalocean.com/community/tutorials/garbage-collection-in-java