Java和C/C++的區(qū)別之一就是自動管理內(nèi)存,即自動分配和回收內(nèi)存。在程序員不能自己管理內(nèi)存的情況下,理解Java如何回收內(nèi)存將有助于提高編碼質(zhì)量。隨著一個程序的運(yùn)行,程序產(chǎn)生的對象、變量等信息將不斷消耗有限的內(nèi)存,及時回收內(nèi)存不僅可以保證內(nèi)存正常運(yùn)行,還可以提高系統(tǒng)性能。本文將圍繞以下問題探討Java垃圾回收:
- 哪些內(nèi)存區(qū)域可以進(jìn)行垃圾回收?
- 如何確定一個對象是否可以回收?
- 垃圾回收算法概覽
1. 哪些內(nèi)存區(qū)域可以進(jìn)行垃圾回收?
在Java虛擬機(jī)中共分五個區(qū)域,分別是線程私有的程序計數(shù)器、Java虛擬機(jī)棧、本地方法棧和線程共享的堆和方法區(qū)。線程私有的內(nèi)存區(qū)域不需要進(jìn)行垃圾回收,因為隨著線程的運(yùn)行結(jié)束,它門占用內(nèi)存自然就被釋放了。因此只有線程共享的堆和方法區(qū)需要進(jìn)行垃圾回收。
確定了需要進(jìn)行垃圾回收的內(nèi)存區(qū)域,下一個問題就是Java如何確定哪些對象是可以回收的呢?
2. 如何確定一個對象是否可以回收?
我們都知道當(dāng)一個對象不再被需要的時候就可以進(jìn)行垃圾回收,在Java中確定一個對象是否可以被回收有如下兩種方法。
2.1 引用計數(shù)器算法
每個對象都會包含一個引用計數(shù)器,當(dāng)對象被引用一次,計數(shù)器加1;當(dāng)對象失去一個引用,計數(shù)器減1。當(dāng)計數(shù)器在一段時間內(nèi)保持為0時,該對象就被虛擬機(jī)認(rèn)為是可回收的。
-
算法缺點
- 無法回收循環(huán)引用的對象:當(dāng)兩個對象互相引用時,二者就都不會被回收,因為計數(shù)器永遠(yuǎn)不會為0。當(dāng)兩個對象沒有任何作用時,這部分內(nèi)存依然被占用無法回收。
-
算法優(yōu)點
- 實現(xiàn)簡單,效率較高。
2.2 GC Roots根搜索算法
根搜索是通過一些“GC roots”對象作為起點,從這些節(jié)點開始往下搜索,搜索通過的路徑則成為引用鏈(Reference Chain)。當(dāng)一個對象沒有被GC Roots的引用鏈連接的時候,則表明該對象沒有任何引用,可以被回收。什么是GC roots呢?GC roots是被JVM引用的對象,因此GC roots可以保證其他被引用的對象不被JVM回收。

GC Roots對象包括:
- 虛擬機(jī)棧(棧幀中的本地變量表)中的引用對象
- 方法區(qū)中的靜態(tài)類或?qū)傩砸玫膶ο?/li>
- 方法區(qū)中常量引用的對象
- 本地方法棧中JNI(Native方法)的引用對象
了解了JVM是如何確定一個對象是否可回收之后,下一步則是JVM是如何回收這些對象的。
3. JVM垃圾回收算法概覽
3.1 標(biāo)記-清除算法(Mark-Sweep)
標(biāo)記清楚算法分兩個階段:“標(biāo)記”和“清除”。首先,JVM將可以回收的對象進(jìn)行標(biāo)記,之后對所有對象進(jìn)行掃描,將有標(biāo)記的對象進(jìn)行回收。
- 算法缺點
算法效率不高,并且在清理完成后會產(chǎn)生內(nèi)存碎片。清理完成后,如果有大對象需要連續(xù)的內(nèi)存空間時,還需要進(jìn)行碎片整理。 - 可視化
-
標(biāo)記
Step 1 : Marking -
清理
Step 2 : Deletion
-
3.2 標(biāo)記整理算法(Mark-Compact)
與標(biāo)記清理算法類似,都是標(biāo)記回收。但是標(biāo)記整理算法在清理之后會將依然存活的對象移動到一起,這就使得內(nèi)存得以連續(xù)。
- 算法優(yōu)點
標(biāo)記整理算法可以提高內(nèi)存使用效率,因為在內(nèi)存整理之后,當(dāng)程序需要為新對象分配空間時就無需在內(nèi)存碎片中搜索大小合適的碎片。 - 可視化
-
整理
Step 3 : Compacting
-
3.3 復(fù)制算法(Copying)
復(fù)制算法將要進(jìn)行垃圾回收的內(nèi)存分成不同的模塊。當(dāng)在A內(nèi)存模塊中進(jìn)行垃圾回收時,將存活的對象復(fù)制到B內(nèi)存模塊,然后清理A內(nèi)存模塊。
算法優(yōu)點
算法實現(xiàn)簡單,運(yùn)行效率高。算法缺點
由于每次只能使用其中一部分內(nèi)存,導(dǎo)致內(nèi)存利用率不高。-
可視化
-
Copy
Copying
-
4. 總結(jié)
很多文章把分代收集(Generational Collection)也列為了一種垃圾回收算法,個人并不認(rèn)同,因為分代收集只是垃圾回收算法的應(yīng)用。比如在HotSpot虛擬機(jī)中共有新生代、老年代和永久代,在不同的分代中使用不同的垃圾回收算法。不同的分區(qū)適用不同的算法:
- 新生代:復(fù)制算法
- 老年代:標(biāo)記整理算法
- 永久代:標(biāo)記整理算法
Oracle在Java Garbage Collection中詳細(xì)介紹了不同的分區(qū)如何使用不同的垃圾回收算法,同時也介紹了對象如何從新生代不斷迭代進(jìn)入永久代的。
除此之外,垃圾回收器也是垃圾回收中重要的一部分。常見的垃圾收集器有:
- 串行GC (Serial GC)
- 并行回收GC (Parallel Scavenge)
- 并行GC (ParNew GC)
不同的垃圾回收器可以通過不同的命令來指定使用,例如:
- -XX:+UseSerialGC
- -XX:+UseParallelGC
等等。



