老生常談java垃圾回收算法(必看篇)
1.引用計數(shù)法(Reference Counting Collector)
1.1算法分析
引用計數(shù)是垃圾收集器中的早期策略。在這種方法中,堆中每個對象實例都有一個引用計數(shù)。當一個對象被創(chuàng)建時,且將該對象實例分配給一個變量,該變量計數(shù)設置為1。當任何其它變量被賦值為這個對象的引用時,計數(shù)加1(a = b,則b引用的對象實例的計數(shù)器+1),但當一個對象實例的某個引用超過了生命周期或者被設置為一個新值時,對象實例的引用計數(shù)器減1。任何引用計數(shù)器為0的對象實例可以被當作垃圾收集。當一個對象實例被垃圾收集時,它引用的任何對象實例的引用計數(shù)器減1。
1.2優(yōu)缺點
優(yōu)點:
引用計數(shù)收集器可以很快的執(zhí)行,交織在程序運行中。對程序需要不被長時間打斷的實時環(huán)境比較有利。
缺點:
無法檢測出循環(huán)引用。如父對象有一個對子對象的引用,子對象反過來引用父對象。這樣,他們的引用計數(shù)永遠不可能為0.
1.3引用計數(shù)算法無法解決循環(huán)引用問題,例如:
public class Main {
public static void main(String[] args) {
MyObject object1 = new MyObject();
MyObject object2 = new MyObject();
object1.object = object2;
object2.object = object1;
object1 = null;
object2 = null;
}
}
最后面兩句將object1和object2賦值為null,也就是說object1和object2指向的對象已經(jīng)不可能再被訪問,但是由于它們互相引用對方,導致它們的引用計數(shù)器都不為0,那么垃圾收集器就永遠不會回收它們。
2.Mark-Sweep(標記-清除)Tracing Collector(tracing算法)
這是最基礎的垃圾回收算法,之所以說它是最基礎的是因為它最容易實現(xiàn),思想也是最簡單的。標記-清除算法分為兩個階段:標記階段和清除階段。標記階段的任務是標記出所有需要被回收的對象,清除階段就是回收被標記的對象所占用的空間。具體過程如下圖所示:

從圖中可以很容易看出標記-清除算法實現(xiàn)起來比較容易,但是有一個比較嚴重的問題就是容易產(chǎn)生內(nèi)存碎片,碎片太多可能會導致后續(xù)過程中需要為大對象分配空間時無法找到足夠的空間而提前觸發(fā)新的一次垃圾收集動作。
3.Copying(復制)算法
為了解決Mark-Sweep算法的缺陷,Copying算法就被提了出來。它將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的內(nèi)存用完了,就將還存活著的對象復制到另外一塊上面,然后再把已使用的內(nèi)存空間一次清理掉,這樣一來就不容易出現(xiàn)內(nèi)存碎片的問題。具體過程如下圖所示:

這種算法雖然實現(xiàn)簡單,運行高效且不容易產(chǎn)生內(nèi)存碎片,但是卻對內(nèi)存空間的使用做出了高昂的代價,因為能夠使用的內(nèi)存縮減到原來的一半。
很顯然,Copying算法的效率跟存活對象的數(shù)目多少有很大的關系,如果存活對象很多,那么Copying算法的效率將會大大降低。
4.Mark-Compact(標記-整理)算法
為了解決Copying算法的缺陷,充分利用內(nèi)存空間,提出了Mark-Compact算法。該算法標記階段和Mark-Sweep一樣,但是在完成標記之后,它不是直接清理可回收對象,而是將存活對象都向一端移動,然后清理掉端邊界以外的內(nèi)存。具體過程如下圖所示:

5.Generational Collection(分代收集)算法
分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根據(jù)對象存活的生命周期將內(nèi)存劃分為若干個不同的區(qū)域。一般情況下將堆區(qū)劃分為老年代(Tenured Generation)和新生代(Young Generation),老年代的特點是每次垃圾收集時只有少量對象需要被回收,而新生代的特點是每次垃圾回收時都有大量的對象需要被回收,那么就可以根據(jù)不同代的特點采取最適合的收集算法。
目前大部分垃圾收集器對于新生代都采取Copying算法,因為新生代中每次垃圾回收都要回收大部分對象,也就是說需要復制的操作次數(shù)較少,但是實際中并不是按照1:1的比例來劃分新生代的空間的,一般來說是將新生代劃分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden空間和其中的一塊Survivor空間,當進行回收時,將Eden和Survivor中還存活的對象復制到另一塊Survivor空間中,然后清理掉Eden和剛才使用過的Survivor空間。
而由于老年代的特點是每次回收都只回收少量對象,一般使用的是Mark-Compact算法。
注意,在堆區(qū)之外還有一個代就是永久代(Permanet Generation),它用來存儲class類、常量、方法描述等。對永久代的回收主要回收兩部分內(nèi)容:廢棄常量和無用的類。
垃圾收集器
新生代收集器使用的收集器:Serial、PraNew、Parallel Scavenge
老年代收集器使用的收集器:Serial Old、Parallel Old、CMS

Serial收集器(復制算法)
新生代單線程收集器,標記和清理都是單線程,優(yōu)點是簡單高效。
Serial Old收集器(標記-整理算法)
老年代單線程收集器,Serial收集器的老年代版本。
ParNew收集器(停止-復制算法)
新生代收集器,可以認為是Serial收集器的多線程版本,在多核CPU環(huán)境下有著比Serial更好的表現(xiàn)。
Parallel Scavenge收集器(停止-復制算法)
并行收集器,追求高吞吐量,高效利用CPU。吞吐量一般為99%, 吞吐量= 用戶線程時間/(用戶線程時間+GC線程時間)。適合后臺應用等對交互相應要求不高的場景。
Parallel Old收集器(停止-復制算法)
Parallel Scavenge收集器的老年代版本,并行收集器,吞吐量優(yōu)先
CMS(Concurrent Mark Sweep)收集器(標記-清理算法)
高并發(fā)、低停頓,追求最短GC回收停頓時間,cpu占用比較高,響應時間快,停頓時間短,多核cpu 追求高響應時間的選擇
GC的執(zhí)行機制
由于對象進行了分代處理,因此垃圾回收區(qū)域、時間也不一樣。GC有兩種類型:Scavenge GC和Full GC。
Scavenge GC
一般情況下,當新對象生成,并且在Eden申請空間失敗時,就會觸發(fā)Scavenge GC,對Eden區(qū)域進行GC,清除非存活對象,并且把尚且存活的對象移動到Survivor區(qū)。然后整理Survivor的兩個區(qū)。這種方式的GC是對年輕代的Eden區(qū)進行,不會影響到年老代。因為大部分對象都是從Eden區(qū)開始的,同時Eden區(qū)不會分配的很大,所以Eden區(qū)的GC會頻繁進行。因而,一般在這里需要使用速度快、效率高的算法,使Eden去能盡快空閑出來。
Full GC
對整個堆進行整理,包括Young、Tenured和Perm。Full GC因為需要對整個堆進行回收,所以比Scavenge GC要慢,因此應該盡可能減少Full GC的次數(shù)。在對JVM調(diào)優(yōu)的過程中,很大一部分工作就是對于FullGC的調(diào)節(jié)。有如下原因可能導致Full GC:
1.年老代(Tenured)被寫滿
2.持久代(Perm)被寫滿
3.System.gc()被顯示調(diào)用
4.上一次GC之后Heap的各域分配策略動態(tài)變化
Java有了GC同樣會出現(xiàn)內(nèi)存泄露問題
靜態(tài)集合類像HashMap、Vector等的使用最容易出現(xiàn)內(nèi)存泄露,這些靜態(tài)變量的生命周期和應用程序一致,所有的對象Object也不能被釋放,因為他們也將一直被Vector等應用著。
Static Vector v = new Vector();
for (int i = 1; i<100; i++)
{
Object o = new Object();
v.add(o);
o = null;
}
在這個例子中,代碼棧中存在Vector 對象的引用 v 和 Object 對象的引用 o 。在 For 循環(huán)中,我們不斷的生成新的對象,然后將其添加到 Vector 對象中,之后將 o 引用置空。問題是當 o 引用被置空后,如果發(fā)生 GC,我們創(chuàng)建的 Object 對象是否能夠被 GC 回收呢?答案是否定的。因為, GC 在跟蹤代碼棧中的引用時,會發(fā)現(xiàn) v 引用,而繼續(xù)往下跟蹤,就會發(fā)現(xiàn) v 引用指向的內(nèi)存空間中又存在指向 Object 對象的引用。也就是說盡管o 引用已經(jīng)被置空,但是 Object 對象仍然存在其他的引用,是可以被訪問到的,所以 GC 無法將其釋放掉。如果在此循環(huán)之后, Object 對象對程序已經(jīng)沒有任何作用,那么我們就認為此 Java 程序發(fā)生了內(nèi)存泄漏。
2.各種連接,數(shù)據(jù)庫連接,網(wǎng)絡連接,IO連接等沒有顯示調(diào)用close關閉,不被GC回收導致內(nèi)存泄露。
3.監(jiān)聽器的使用,在釋放對象的同時沒有相應刪除監(jiān)聽器的時候也可能導致內(nèi)存泄露。
以上這篇(標題)就是小編分享給大家的全部內(nèi)容了,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
spring的構造函數(shù)注入屬性@ConstructorBinding用法
這篇文章主要介紹了關于spring的構造函數(shù)注入屬性@ConstructorBinding用法,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12
Java中byte輸出write到文件的實現(xiàn)方法講解
今天小編就為大家分享一篇關于Java中byte輸出write到文件的實現(xiàn)方法講解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-03-03
關于@GetMapping和@GetMapping(value=““)的區(qū)別
這篇文章主要介紹了關于@GetMapping和@GetMapping(value=““)的區(qū)別說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-05-05
Spring使用@Async出現(xiàn)循環(huán)依賴原因及解決方案分析
在Spring框架中,啟用異步功能需要在應用主類上添加@EnableAsync注解,當項目中存在循環(huán)引用時,如一個異步類MessageService和一個常規(guī)類TaskService相互引用,并且這兩個類位于同一包內(nèi),這種情況下可能會觸發(fā)Spring的循環(huán)依賴異常2024-10-10
在eclipse中使用SVN的實現(xiàn)方法(圖文教程)
這篇文章主要介紹了在eclipse中使用SVN的實現(xiàn)方法(圖文教程),文中通過圖文介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-07-07

