傳統的C/C++等程式語言,需要程式設計師負責回收已經分配的記憶體。顯示進行垃圾回收是一件比較困難的事情,因為程式設計師並不總是知道記憶體應該何時被釋放。如果一些分配出去的記憶體得不及時回收,就會引起系統執行速度下降,甚至導致程式癱瘓,這種現象被稱為記憶體洩漏。總體而言,顯示進行垃圾回收主要有如下兩個缺點:
1.程式忘記及時回收無用記憶體,從而導致記憶體洩漏,降低系統效能。
2.程式錯誤地回收程式核心類庫地記憶體,從而導致系統崩潰。
與 C/C++程式不同,Java 語言不需要程式設計師直接控制記憶體回收,Java 程式的記憶體分配和回收都是由JRE(JAVA執行時環境)在後臺自動進行的。JRE 會負責回收那些不再使用的記憶體,這種機制被稱為垃圾回收(CrarbgrgSolection,GC)。通常 JRE會提供一個後臺執行緒來進行檢測和控制。一感都是在CPU空閒或記憶體不足時自動進行垃圾回收,而程式設計師無法精確控制垃圾回收的時間和順序等。
Java 的推記憶體是一個執行時資料區,用以儲存類的例項(物件),Java虛擬機器的堆記憶體中儲存著正在執行的應用程式所建立的所有物件,這些物件不需要程式透過程式碼來顯示地釋放。一般來說,堆記憶體的回收由垃圾回收器來負責,所有的JVM 實現都有一個由垃圾回收器管理的堆記憶體。垃圾回收是一種動態儲存管理技術,它自動形放不再被程式引用地物件,技照特定的垃圾回收演算法來實現記憶體資源的自動回收功能。
在C/C++中,物件所佔用的記憶體不會被自動釋放,如果程式沒有顯式釋放物件所佔用的記憶體,物件所佔用的記憶體就不能分配給其他物件,該記憶體在程式結束執行之前將一直被佔用:而在Java 中,當沒有引用變數指向原先分配給某個物件的記憶體時。該記憶體便成為垃圾。JVM的一個超級執行緒會自動釋放該記憶體區。垃圾回收意味著程式不再需要的物件是“垃圾資訊”這些資訊將被丟棄。
當一個物件不再被引用時,記憶體回收它佔領的空間,以便空間被後來的新物件使用。事實上,除釋放沒用的物件外,垃圾回收也可以清除記憶體記錄碎片。由於建立物件和垃圾回收器釋放丟棄物件所佔的記憶體空間,記憶體會出現碎片。碎片是分配給物件的記憶體塊之間的空閒記憶體區,碎片整理將所佔用的堆記憶體移到堆的一端,JVM將整理出的記憶體分配給新的物件。
垃圾回收能自動釋放記憶體空間,減輕程式設計的負擔,這使Java 虛擬機器具有兩個顯著的優點。
>垃圾回收機制可以很好地提高程式設計效率。在沒有垃圾回收機制時,可能要花許多時間來解決一個難懂的儲存器問題。在用Java語言程式設計時,依靠垃圾回收機制可大大縮短時間。
>垃圾回收機制保護程式的完整性,垃圾回收是Java 語言安全性策略的一個重要部分。
垃圾回收的一個潛在缺點是它的開銷影響程式效能。Java 虛擬機器必須跟蹤程式中有用的物件,才可以確定哪些物件是無用的物件,並最終釋放這些無用的物件。這個過程需要花費處理器的時間。其次是垃圾回收演算法的不完備性,早先採用的某些垃圾回收演算法就不能保證100%收集到所有的廢棄記憶體。當然,隨著垃坡回收演算法的不斷改進,以及軟硬體執行效率的不斷提升,這些問題都可以迎刃而解。
Java 語言規範沒有明確地說明JVM使用哪種垃圾回收演算法,但是任何一種垃圾回收演算法一般要做兩件基本的事情:1.發現無用的物件2.回收被無用物件佔用的記憶體空間,使該空間可被程式再次使用。
通常,垃圾回收具有如下幾個特點。
>垃圾回收器的工作目標是回收無用物件的記憶體空間,這些記憶體空間都是JVM堆記憶體裡的記憶體空間,垃圾回收器只能回收記憶體資源,對其他物理資源,如資料庫連線,磁碟 I/O 等資源則無能為 。
>為了更快地讓垃圾回收器回收那些不再使用的物件,可以將該物件的引用變數設定為 null,透過這種方式暗示垃圾回收器可以回收該物件。
>垃圾回收發生的不可預知性。由於不同JVM採用了不同的垃圾回收機制和不同的垃圾回收演算法,因此它有可能是定時發生的,有可能是當CPU空閒時發生的,也有可能和原始的垃圾回收一樣,等到記憶體消耗出現極限時發生,這和垃圾回收實現機制的選擇及具體的設定都有關係。雖然程式設計師可以透過呼叫 Runtime 物件的 gc()或 System.gc()等方法來建議系統進行垃極回收,但這種呼叫僅僅是建議,依然不能精確控制垃圾回收機制的執行。
>垃圾回收的精確性主要包括兩個方面:一是垃極回收機制能夠精確地標記活者的物件;二是垃圾回收器能夠精確的定位物件之間的引用關係,前者是完全回收所有廢棄物件的前提,否則就能造成記憶體洩露;而後者則是實現歸併和複製等演算法的必要條件,透過這種引用關係,可以保證所有物件都能被可靠地回收,所有物件都能夠重新分配。從而有效的減少記憶體碎片的產生。
>現在的JVM 有多種不同的垃圾回收實現,每種回收機制因其演算法差異可能表現各異。有的當垃圾回收開始時就停止應用程式的執行,有的當垃圾回收執行時允許應用程式的執行緒執行,還有的在同一時間允許垃圾回收多執行緒執行。
當編寫 Java 程式時,一個基本原則是:對於不再需要的物件,不要引用它們。如果保持對這些種象的引用,垃圾回收機制暫時不會回收該物件,則會導致系統可用記憶體越來越少;當系統可用記憶體越來越少時,垃圾回收執行的頻率就越來越高,從而導致系統的效能下降。
2011年7月釋出的Java 7 提供了G1垃圾回收器來代替原有的並行標記/清除垃圾回收器(簡種CMS)。並宣佈在未來的日子裡,G1垃圾回收器將會逐漸取代原有的CMS垃圾回收器。
2014年3月釋出的 Java 8刪除了 HotSpot JVM 中的永生代記憶體(PermGen,永生代記憶體主要用於儲存一些需要常駐記憶體、通常不會被回收的資訊),而是改為使用本地記憶體來儲存類的後設資料資訊,並將之稱為:元空間(Metaspace),這意味著以後不會再遇到 java.lang.OutOfMemoryEror:PermGen 錯誤(曾經令許多Java 程式設計師頭痛的錯誤)。
2017年9月釋出的Java9徹底刪除了傳統的CMS 垃圾回收器,因此執行JVM的 DefNew + CMS.ParNew + SerialOld、Incremental CMS 等組合全部失效。java 命令(該命令負責啟用JVM執行Java 程式)以前支援的以下GC相關選項全部被刪除。
>-Xincgc
> -XX:+CMSIncrementalMode
>-XX:+UseCMSCompactAtFullCollection
> -XX:+CMSFullGCsBeforeCompaction
> -XX:+UseCMSCollectionPassing
此外,-XX:+UseParNewGC 選項也被標記為過時,將來也會被刪除。
Java 9預設採用低暫停(low-pause)的Gl垃圾回收器,併為G1垃圾回收器自動確定了幾個重要的引數設定,從而保證GI垃圾回收器的可用性、確定性和效能。如果部署專案時為 java 命令指定了
-XX:+UseConcMarkSweepGC選項希望啟用 CMS垃圾回收器,系統會顯示警告資訊。
Java 11 則再次引入了新的、實驗性的z垃圾回收器(簡稱 ZGC),這個垃圾回收器具有以下幾個優點。
> 垃圾回收時暫停時間不會超過10ms。
> 暫停時間不會隨著堆或實時集合的大小而增加。
>可處理幾百MB到幾 TB 的堆記憶體。
由於 ZGC的核心是併發垃圾回收器,這意味官可在Java執行緒繼續執行時,完成所有的繁重工作(如標記、壓縮、引用處理、表清理等),從而大大降低了該垃圾回收器對程式響應速度的影響。
由於 ZGC在Java 11 中還處於實驗性階段〈在未米可能取代G1 垃圾回收器),因此 Java 預設並未啟用 ZGC 垃圾回收器。如果希望執行 Java 程式時啟用 ZGC垃圾回收器,則可在執行 java.命令時使用如下選項。
> -XX:+UnlockExperimentalVMOptions
> -XX:+UseZGC
目前ZGC 垃圾回收器只能在64位的Linux 平臺上使用。
此外,Java 11還引入了實驗性的Epsiion 垃圾回收器。嚴格來說。Epsilon 並不算真正的垃圾回收器,它只負責記憶體分配,並不負責記憶體回收。因此,這個垃圾回收器主要在效能測試中比較有用,用於與其他垃圾回收器的開銷/收益比進行對比。