Java對象的生命週期由JVM管理。當程式代碼中創建了一個對象以後,不必擔心它生命週期的其他部分。JVM將自動查找那些不再使用的對象,並從堆中回收它們的記憶體。
垃圾收集是JVM的一項主要操作,根據我們的需求進行調整可以為應用程式帶來巨大的性能提升。現代JVM提供了各種垃圾收集演算法。我們需要瞭解應用程式需要決定使用哪種演算法。
無法在Java中以編程方式釋放對象,這不像在C和C++等非GC語言中一樣。因此,不能在Java中使用懸空引用。但是,可能具有空引用(引用指向JVM不會存儲對象的記憶體區域)。每當使用空引用時,JVM都會拋出NullPointerException
異常。
請注意,由於GC,很少在Java程式中發現記憶體洩漏,但它們確實發生了。我們將在本章末尾創建一個記憶體洩漏。
現代JVM使用以下GC:
- 串行收集器
- 吞吐量收集器
- CMS收集器
- G1收集器
上述每個演算法都執行相同的任務 - 查找不再使用的對象並回收它們在堆中佔用的記憶體。其中有一種比較靠譜的方法是計算每個對象具有的引用數量,並在引用數量變為0
時將其釋放(這也稱為引用計數)。為什麼靠譜? 以迴圈鏈表為例。鏈表的每個節點都有一個對它的引用,但整個對象不是從任何地方引用時,理想情況下它應該被釋放。
JVM不僅可以釋放記憶體,還可以將小記憶體卡盤合併到更大的記憶體中。這樣做是為了防止記憶體碎片。
簡單來說,典型的GC演算法可以執行以下活動 -
- 查找未使用的對象;
- 釋放它們在堆中佔用的記憶體;
- 合併碎片;
GC必須在運行時停止應用程式線程。這是因為它在運行時移動對象,因此無法使用這些對象。這種停頓被稱為“世界停頓”,並且在調整我們的GC時,最小化這些停頓的頻率和持續時間是我們的目標。
記憶體合併
下麵顯示了記憶體合併的示例 -
陰影部分是需要釋放的對象。即使在回收所有空間之後,我們也只能分配最大記憶體等於75Kb
的對象。即使有200Kb
的可用空間,如下所示: