老生常談java垃圾回收算法(必看篇)
1.引用計(jì)數(shù)法(Reference Counting Collector)
1.1算法分析
引用計(jì)數(shù)是垃圾收集器中的早期策略。在這種方法中,堆中每個(gè)對(duì)象實(shí)例都有一個(gè)引用計(jì)數(shù)。當(dāng)一個(gè)對(duì)象被創(chuàng)建時(shí),且將該對(duì)象實(shí)例分配給一個(gè)變量,該變量計(jì)數(shù)設(shè)置為1。當(dāng)任何其它變量被賦值為這個(gè)對(duì)象的引用時(shí),計(jì)數(shù)加1(a = b,則b引用的對(duì)象實(shí)例的計(jì)數(shù)器+1),但當(dāng)一個(gè)對(duì)象實(shí)例的某個(gè)引用超過了生命周期或者被設(shè)置為一個(gè)新值時(shí),對(duì)象實(shí)例的引用計(jì)數(shù)器減1。任何引用計(jì)數(shù)器為0的對(duì)象實(shí)例可以被當(dāng)作垃圾收集。當(dāng)一個(gè)對(duì)象實(shí)例被垃圾收集時(shí),它引用的任何對(duì)象實(shí)例的引用計(jì)數(shù)器減1。
1.2優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
引用計(jì)數(shù)收集器可以很快的執(zhí)行,交織在程序運(yùn)行中。對(duì)程序需要不被長時(shí)間打斷的實(shí)時(shí)環(huán)境比較有利。
缺點(diǎn):
無法檢測(cè)出循環(huán)引用。如父對(duì)象有一個(gè)對(duì)子對(duì)象的引用,子對(duì)象反過來引用父對(duì)象。這樣,他們的引用計(jì)數(shù)永遠(yuǎn)不可能為0.
1.3引用計(jì)數(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指向的對(duì)象已經(jīng)不可能再被訪問,但是由于它們互相引用對(duì)方,導(dǎo)致它們的引用計(jì)數(shù)器都不為0,那么垃圾收集器就永遠(yuǎn)不會(huì)回收它們。
2.Mark-Sweep(標(biāo)記-清除)Tracing Collector(tracing算法)
這是最基礎(chǔ)的垃圾回收算法,之所以說它是最基礎(chǔ)的是因?yàn)樗钊菀讓?shí)現(xiàn),思想也是最簡(jiǎn)單的。標(biāo)記-清除算法分為兩個(gè)階段:標(biāo)記階段和清除階段。標(biāo)記階段的任務(wù)是標(biāo)記出所有需要被回收的對(duì)象,清除階段就是回收被標(biāo)記的對(duì)象所占用的空間。具體過程如下圖所示:
從圖中可以很容易看出標(biāo)記-清除算法實(shí)現(xiàn)起來比較容易,但是有一個(gè)比較嚴(yán)重的問題就是容易產(chǎn)生內(nèi)存碎片,碎片太多可能會(huì)導(dǎo)致后續(xù)過程中需要為大對(duì)象分配空間時(shí)無法找到足夠的空間而提前觸發(fā)新的一次垃圾收集動(dòng)作。
3.Copying(復(fù)制)算法
為了解決Mark-Sweep算法的缺陷,Copying算法就被提了出來。它將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當(dāng)這一塊的內(nèi)存用完了,就將還存活著的對(duì)象復(fù)制到另外一塊上面,然后再把已使用的內(nèi)存空間一次清理掉,這樣一來就不容易出現(xiàn)內(nèi)存碎片的問題。具體過程如下圖所示:
這種算法雖然實(shí)現(xiàn)簡(jiǎn)單,運(yùn)行高效且不容易產(chǎn)生內(nèi)存碎片,但是卻對(duì)內(nèi)存空間的使用做出了高昂的代價(jià),因?yàn)槟軌蚴褂玫膬?nèi)存縮減到原來的一半。
很顯然,Copying算法的效率跟存活對(duì)象的數(shù)目多少有很大的關(guān)系,如果存活對(duì)象很多,那么Copying算法的效率將會(huì)大大降低。
4.Mark-Compact(標(biāo)記-整理)算法
為了解決Copying算法的缺陷,充分利用內(nèi)存空間,提出了Mark-Compact算法。該算法標(biāo)記階段和Mark-Sweep一樣,但是在完成標(biāo)記之后,它不是直接清理可回收對(duì)象,而是將存活對(duì)象都向一端移動(dòng),然后清理掉端邊界以外的內(nèi)存。具體過程如下圖所示:
5.Generational Collection(分代收集)算法
分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根據(jù)對(duì)象存活的生命周期將內(nèi)存劃分為若干個(gè)不同的區(qū)域。一般情況下將堆區(qū)劃分為老年代(Tenured Generation)和新生代(Young Generation),老年代的特點(diǎn)是每次垃圾收集時(shí)只有少量對(duì)象需要被回收,而新生代的特點(diǎn)是每次垃圾回收時(shí)都有大量的對(duì)象需要被回收,那么就可以根據(jù)不同代的特點(diǎn)采取最適合的收集算法。
目前大部分垃圾收集器對(duì)于新生代都采取Copying算法,因?yàn)樾律忻看卫厥斩家厥沾蟛糠謱?duì)象,也就是說需要復(fù)制的操作次數(shù)較少,但是實(shí)際中并不是按照1:1的比例來劃分新生代的空間的,一般來說是將新生代劃分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden空間和其中的一塊Survivor空間,當(dāng)進(jìn)行回收時(shí),將Eden和Survivor中還存活的對(duì)象復(fù)制到另一塊Survivor空間中,然后清理掉Eden和剛才使用過的Survivor空間。
而由于老年代的特點(diǎn)是每次回收都只回收少量對(duì)象,一般使用的是Mark-Compact算法。
注意,在堆區(qū)之外還有一個(gè)代就是永久代(Permanet Generation),它用來存儲(chǔ)class類、常量、方法描述等。對(duì)永久代的回收主要回收兩部分內(nèi)容:廢棄常量和無用的類。
垃圾收集器
新生代收集器使用的收集器:Serial、PraNew、Parallel Scavenge
老年代收集器使用的收集器:Serial Old、Parallel Old、CMS
Serial收集器(復(fù)制算法)
新生代單線程收集器,標(biāo)記和清理都是單線程,優(yōu)點(diǎn)是簡(jiǎn)單高效。
Serial Old收集器(標(biāo)記-整理算法)
老年代單線程收集器,Serial收集器的老年代版本。
ParNew收集器(停止-復(fù)制算法)
新生代收集器,可以認(rèn)為是Serial收集器的多線程版本,在多核CPU環(huán)境下有著比Serial更好的表現(xiàn)。
Parallel Scavenge收集器(停止-復(fù)制算法)
并行收集器,追求高吞吐量,高效利用CPU。吞吐量一般為99%, 吞吐量= 用戶線程時(shí)間/(用戶線程時(shí)間+GC線程時(shí)間)。適合后臺(tái)應(yīng)用等對(duì)交互相應(yīng)要求不高的場(chǎng)景。
Parallel Old收集器(停止-復(fù)制算法)
Parallel Scavenge收集器的老年代版本,并行收集器,吞吐量優(yōu)先
CMS(Concurrent Mark Sweep)收集器(標(biāo)記-清理算法)
高并發(fā)、低停頓,追求最短GC回收停頓時(shí)間,cpu占用比較高,響應(yīng)時(shí)間快,停頓時(shí)間短,多核cpu 追求高響應(yīng)時(shí)間的選擇
GC的執(zhí)行機(jī)制
由于對(duì)象進(jìn)行了分代處理,因此垃圾回收區(qū)域、時(shí)間也不一樣。GC有兩種類型:Scavenge GC和Full GC。
Scavenge GC
一般情況下,當(dāng)新對(duì)象生成,并且在Eden申請(qǐng)空間失敗時(shí),就會(huì)觸發(fā)Scavenge GC,對(duì)Eden區(qū)域進(jìn)行GC,清除非存活對(duì)象,并且把尚且存活的對(duì)象移動(dòng)到Survivor區(qū)。然后整理Survivor的兩個(gè)區(qū)。這種方式的GC是對(duì)年輕代的Eden區(qū)進(jìn)行,不會(huì)影響到年老代。因?yàn)榇蟛糠謱?duì)象都是從Eden區(qū)開始的,同時(shí)Eden區(qū)不會(huì)分配的很大,所以Eden區(qū)的GC會(huì)頻繁進(jìn)行。因而,一般在這里需要使用速度快、效率高的算法,使Eden去能盡快空閑出來。
Full GC
對(duì)整個(gè)堆進(jìn)行整理,包括Young、Tenured和Perm。Full GC因?yàn)樾枰獙?duì)整個(gè)堆進(jìn)行回收,所以比Scavenge GC要慢,因此應(yīng)該盡可能減少Full GC的次數(shù)。在對(duì)JVM調(diào)優(yōu)的過程中,很大一部分工作就是對(duì)于FullGC的調(diào)節(jié)。有如下原因可能導(dǎo)致Full GC:
1.年老代(Tenured)被寫滿
2.持久代(Perm)被寫滿
3.System.gc()被顯示調(diào)用
4.上一次GC之后Heap的各域分配策略動(dòng)態(tài)變化
Java有了GC同樣會(huì)出現(xiàn)內(nèi)存泄露問題
靜態(tài)集合類像HashMap、Vector等的使用最容易出現(xiàn)內(nèi)存泄露,這些靜態(tài)變量的生命周期和應(yīng)用程序一致,所有的對(duì)象Object也不能被釋放,因?yàn)樗麄円矊⒁恢北籚ector等應(yīng)用著。
Static Vector v = new Vector(); for (int i = 1; i<100; i++) { Object o = new Object(); v.add(o); o = null; }
在這個(gè)例子中,代碼棧中存在Vector 對(duì)象的引用 v 和 Object 對(duì)象的引用 o 。在 For 循環(huán)中,我們不斷的生成新的對(duì)象,然后將其添加到 Vector 對(duì)象中,之后將 o 引用置空。問題是當(dāng) o 引用被置空后,如果發(fā)生 GC,我們創(chuàng)建的 Object 對(duì)象是否能夠被 GC 回收呢?答案是否定的。因?yàn)椋?GC 在跟蹤代碼棧中的引用時(shí),會(huì)發(fā)現(xiàn) v 引用,而繼續(xù)往下跟蹤,就會(huì)發(fā)現(xiàn) v 引用指向的內(nèi)存空間中又存在指向 Object 對(duì)象的引用。也就是說盡管o 引用已經(jīng)被置空,但是 Object 對(duì)象仍然存在其他的引用,是可以被訪問到的,所以 GC 無法將其釋放掉。如果在此循環(huán)之后, Object 對(duì)象對(duì)程序已經(jīng)沒有任何作用,那么我們就認(rèn)為此 Java 程序發(fā)生了內(nèi)存泄漏。
2.各種連接,數(shù)據(jù)庫連接,網(wǎng)絡(luò)連接,IO連接等沒有顯示調(diào)用close關(guān)閉,不被GC回收導(dǎo)致內(nèi)存泄露。
3.監(jiān)聽器的使用,在釋放對(duì)象的同時(shí)沒有相應(yīng)刪除監(jiān)聽器的時(shí)候也可能導(dǎo)致內(nèi)存泄露。
以上這篇(標(biāo)題)就是小編分享給大家的全部內(nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
spring的構(gòu)造函數(shù)注入屬性@ConstructorBinding用法
這篇文章主要介紹了關(guān)于spring的構(gòu)造函數(shù)注入屬性@ConstructorBinding用法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12java Date獲取年月日時(shí)分秒的實(shí)現(xiàn)方法
下面小編就為大家?guī)硪黄猨ava Date獲取年月日時(shí)分秒的實(shí)現(xiàn)方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-06-06Java中byte輸出write到文件的實(shí)現(xiàn)方法講解
今天小編就為大家分享一篇關(guān)于Java中byte輸出write到文件的實(shí)現(xiàn)方法講解,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-03-03Java使用ExecutorService來停止線程服務(wù)
這篇文章主要介紹了Java使用ExecutorService來停止線程服務(wù),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04關(guān)于@GetMapping和@GetMapping(value=““)的區(qū)別
這篇文章主要介紹了關(guān)于@GetMapping和@GetMapping(value=““)的區(qū)別說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-05-05Spring使用@Async出現(xiàn)循環(huán)依賴原因及解決方案分析
在Spring框架中,啟用異步功能需要在應(yīng)用主類上添加@EnableAsync注解,當(dāng)項(xiàng)目中存在循環(huán)引用時(shí),如一個(gè)異步類MessageService和一個(gè)常規(guī)類TaskService相互引用,并且這兩個(gè)類位于同一包內(nèi),這種情況下可能會(huì)觸發(fā)Spring的循環(huán)依賴異常2024-10-10在eclipse中使用SVN的實(shí)現(xiàn)方法(圖文教程)
這篇文章主要介紹了在eclipse中使用SVN的實(shí)現(xiàn)方法(圖文教程),文中通過圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07