溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

淺談JAVA垃圾回收機(jī)制

發(fā)布時(shí)間:2020-07-18 17:33:45 來(lái)源:億速云 閱讀:134 作者:小豬 欄目:編程語(yǔ)言

小編這次要給大家分享的是淺談JAVA垃圾回收機(jī)制,文章內(nèi)容豐富,感興趣的小伙伴可以來(lái)了解一下,希望大家閱讀完這篇文章之后能夠有所收獲。

說(shuō)到垃圾回收(Garbage Collection,GC),很多人就會(huì)自然而然地把它和Java聯(lián)系起來(lái)。在Java中,程序員不需要去關(guān)心內(nèi)存動(dòng)態(tài)分配和垃圾回收的問(wèn)題,這一切都交給了JVM來(lái)處理。顧名思義,垃圾回收就是釋放垃圾占用的空間,那么在Java中,什么樣的對(duì)象會(huì)被認(rèn)定為“垃圾”?那么當(dāng)一些對(duì)象被確定為垃圾之后,采用什么樣的策略來(lái)進(jìn)行回收(釋放空間)?在目前的商業(yè)虛擬機(jī)中,有哪些典型的垃圾收集器?下面我們就來(lái)逐一探討這些問(wèn)題。以下是本文的目錄大綱:

如果有不正之處,希望諒解和批評(píng)指正,不勝感激?!?/p>

一.如何確定某個(gè)對(duì)象是“垃圾”?

在這一小節(jié)我們先了解一個(gè)最基本的問(wèn)題:如果確定某個(gè)對(duì)象是“垃圾”?既然垃圾收集器的任務(wù)是回收垃圾對(duì)象所占的空間供新的對(duì)象使用,那么垃圾收集器如何確定某個(gè)對(duì)象是“垃圾”?—即通過(guò)什么方法判斷一個(gè)對(duì)象可以被回收了。

在java中是通過(guò)引用來(lái)和對(duì)象進(jìn)行關(guān)聯(lián)的,也就是說(shuō)如果要操作對(duì)象,必須通過(guò)引用來(lái)進(jìn)行。那么很顯然一個(gè)簡(jiǎn)單的辦法就是通過(guò)引用計(jì)數(shù)來(lái)判斷一個(gè)對(duì)象是否可以被回收。不失一般性,如果一個(gè)對(duì)象沒(méi)有任何引用與之關(guān)聯(lián),則說(shuō)明該對(duì)象基本不太可能在其他地方被使用到,那么這個(gè)對(duì)象就成為可被回收的對(duì)象了。這種方式成為引用計(jì)數(shù)法。

這種方式的特點(diǎn)是實(shí)現(xiàn)簡(jiǎn)單,而且效率較高,但是它無(wú)法解決循環(huán)引用的問(wèn)題,因此在Java中并沒(méi)有采用這種方式(Python采用的是引用計(jì)數(shù)法)??聪旅孢@段代碼:

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;
 }
}

class MyObject{
 public Object object = null;
}

最后面兩句將object1和object2賦值為null,也就是說(shuō)object1和object2指向的對(duì)象已經(jīng)不可能再被訪問(wèn),但是由于它們互相引用對(duì)方,導(dǎo)致它們的引用計(jì)數(shù)都不為0,那么垃圾收集器就永遠(yuǎn)不會(huì)回收它們。

為了解決這個(gè)問(wèn)題,在Java中采取了 可達(dá)性分析法。該方法的基本思想是通過(guò)一系列的“GC Roots”對(duì)象作為起點(diǎn)進(jìn)行搜索,如果在“GC Roots”和一個(gè)對(duì)象之間沒(méi)有可達(dá)路徑,則稱該對(duì)象是不可達(dá)的,不過(guò)要注意的是被判定為不可達(dá)的對(duì)象不一定就會(huì)成為可回收對(duì)象。被判定為不可達(dá)的對(duì)象要成為可回收對(duì)象必須至少經(jīng)歷兩次標(biāo)記過(guò)程,如果在這兩次標(biāo)記過(guò)程中仍然沒(méi)有逃脫成為可回收對(duì)象的可能性,則基本上就真的成為可回收對(duì)象了。

至于可達(dá)性分析法具體是如何操作的我暫時(shí)也沒(méi)有看得很明白,如果有哪位朋友比較清楚的話請(qǐng)不吝指教。

下面來(lái)看個(gè)例子:

Object aobj = new Object ( ) ;
Object bobj = new Object ( ) ;
Object cobj = new Object ( ) ;
aobj = bobj;
aobj = cobj;
cobj = null;
aobj = null;

第幾行有可能會(huì)使得某個(gè)對(duì)象成為可回收對(duì)象?第7行的代碼會(huì)導(dǎo)致有對(duì)象會(huì)成為可回收對(duì)象。至于為什么留給讀者自己思考。

再看一個(gè)例子:

String str = new String("hello");
SoftReference<String> sr = new SoftReference<String>(new String("java"));
WeakReference<String> wr = new WeakReference<String>(new String("world"));

這三句哪句會(huì)使得String對(duì)象成為可回收對(duì)象?第2句和第3句,第2句在內(nèi)存不足的情況下會(huì)將String對(duì)象判定為可回收對(duì)象,第3句無(wú)論什么情況下String對(duì)象都會(huì)被判定為可回收對(duì)象。

最后總結(jié)一下平常遇到的比較常見(jiàn)的將對(duì)象判定為可回收對(duì)象的情況:

(1) 顯示地將某個(gè)引用賦值為null或者將已經(jīng)指向某個(gè)對(duì)象的引用指向新的對(duì)象,比如下面的代碼:

Object obj = new Object();
obj = null;
Object obj1 = new Object();
Object obj2 = new Object();
obj1 = obj2;

(2) 局部引用所指向的對(duì)象,比如下面這段代碼:

void fun() {

.....
 for(int i=0;i<10;i++) {
 Object obj = new Object();
 System.out.println(obj.getClass());
 } 
}

(3) 只有弱引用與其關(guān)聯(lián)的對(duì)象,比如:

WeakReference<String> wr = new WeakReference<String>(new String("world"));

二.典型的垃圾收集算法

在確定了哪些垃圾可以被回收后,垃圾收集器要做的事情就是開(kāi)始進(jìn)行垃圾回收,但是這里面涉及到一個(gè)問(wèn)題是:如何高效地進(jìn)行垃圾回收。由于Java虛擬機(jī)規(guī)范并沒(méi)有對(duì)如何實(shí)現(xiàn)垃圾收集器做出明確的規(guī)定,因此各個(gè)廠商的虛擬機(jī)可以采用不同的方式來(lái)實(shí)現(xiàn)垃圾收集器,所以在此只討論幾種常見(jiàn)的垃圾收集算法的核心思想。

1.Mark-Sweep(標(biāo)記-清除)算法

這是最基礎(chǔ)的垃圾回收算法,之所以說(shuō)它是最基礎(chǔ)的是因?yàn)樗钊菀讓?shí)現(xiàn),思想也是最簡(jiǎn)單的。標(biāo)記-清除算法分為兩個(gè)階段:標(biāo)記階段和清除階段。標(biāo)記階段的任務(wù)是標(biāo)記出所有需要被回收的對(duì)象,清除階段就是回收被標(biāo)記的對(duì)象所占用的空間。具體過(guò)程如下圖所示:

淺談JAVA垃圾回收機(jī)制

從圖中可以很容易看出標(biāo)記-清除算法實(shí)現(xiàn)起來(lái)比較容易,但是有一個(gè)比較嚴(yán)重的問(wèn)題就是容易產(chǎn)生內(nèi)存碎片,碎片太多可能會(huì)導(dǎo)致后續(xù)過(guò)程中需要為大對(duì)象分配空間時(shí)無(wú)法找到足夠的空間而提前觸發(fā)新的一次垃圾收集動(dòng)作。

2.Copying(復(fù)制)算法

為了解決Mark-Sweep算法的缺陷,Copying算法就被提了出來(lái)。它將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當(dāng)這一塊的內(nèi)存用完了,就將還存活著的對(duì)象復(fù)制到另外一塊上面,然后再把已使用的內(nèi)存空間一次清理掉,這樣一來(lái)就不容易出現(xiàn)內(nèi)存碎片的問(wèn)題。具體過(guò)程如下圖所示:

淺談JAVA垃圾回收機(jī)制

這種算法雖然實(shí)現(xiàn)簡(jiǎn)單,運(yùn)行高效且不容易產(chǎn)生內(nèi)存碎片,但是卻對(duì)內(nèi)存空間的使用做出了高昂的代價(jià),因?yàn)槟軌蚴褂玫膬?nèi)存縮減到原來(lái)的一半。

很顯然,Copying算法的效率跟存活對(duì)象的數(shù)目多少有很大的關(guān)系,如果存活對(duì)象很多,那么Copying算法的效率將會(huì)大大降低。

3.Mark-Compact(標(biāo)記-整理)算法

為了解決Copying算法的缺陷,充分利用內(nèi)存空間,提出了Mark-Compact算法。該算法標(biāo)記階段和Mark-Sweep一樣,但是在完成標(biāo)記之后,它不是直接清理可回收對(duì)象,而是將存活對(duì)象都向一端移動(dòng),然后清理掉端邊界以外的內(nèi)存。具體過(guò)程如下圖所示:

淺談JAVA垃圾回收機(jī)制

4.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ì)象,也就是說(shuō)需要復(fù)制的操作次數(shù)較少,但是實(shí)際中并不是按照1:1的比例來(lái)劃分新生代的空間的,一般來(lái)說(shuō)是將新生代劃分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden空間和其中的一塊Survivor空間,當(dāng)進(jìn)行回收時(shí),將Eden和Survivor中還存活的對(duì)象復(fù)制到另一塊Survivor空間中,然后清理掉Eden和剛才使用過(guò)的Survivor空間。

而由于老年代的特點(diǎn)是每次回收都只回收少量對(duì)象,一般使用的是Mark-Compact算法。

注意,在堆區(qū)之外還有一個(gè)代就是永久代(Permanet Generation),它用來(lái)存儲(chǔ)class類、常量、方法描述等。對(duì)永久代的回收主要回收兩部分內(nèi)容:廢棄常量和無(wú)用的類。

三.典型的垃圾收集器

垃圾收集算法是 內(nèi)存回收的理論基礎(chǔ),而垃圾收集器就是內(nèi)存回收的具體實(shí)現(xiàn)。下面介紹一下HotSpot(JDK 7)虛擬機(jī)提供的幾種垃圾收集器,用戶可以根據(jù)自己的需求組合出各個(gè)年代使用的收集器。

1.Serial/Serial Old

Serial/Serial Old收集器是最基本最古老的收集器,它是一個(gè)單線程收集器,并且在它進(jìn)行垃圾收集時(shí),必須暫停所有用戶線程。Serial收集器是針對(duì)新生代的收集器,采用的是Copying算法,Serial Old收集器是針對(duì)老年代的收集器,采用的是Mark-Compact算法。它的優(yōu)點(diǎn)是實(shí)現(xiàn)簡(jiǎn)單高效,但是缺點(diǎn)是會(huì)給用戶帶來(lái)停頓。

2.ParNew

ParNew收集器是Serial收集器的多線程版本,使用多個(gè)線程進(jìn)行垃圾收集。

3.Parallel Scavenge

Parallel Scavenge收集器是一個(gè)新生代的多線程收集器(并行收集器),它在回收期間不需要暫停其他用戶線程,其采用的是Copying算法,該收集器與前兩個(gè)收集器有所不同,它主要是為了達(dá)到一個(gè)可控的吞吐量。

4.Parallel Old

Parallel Old是Parallel Scavenge收集器的老年代版本(并行收集器),使用多線程和Mark-Compact算法。

5.CMS

CMS(Current Mark Sweep)收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器,它是一種并發(fā)收集器,采用的是Mark-Sweep算法。

6.G1

G1收集器是當(dāng)今收集器技術(shù)發(fā)展最前沿的成果,它是一款面向服務(wù)端應(yīng)用的收集器,它能充分利用多CPU、多核環(huán)境。因此它是一款并行與并發(fā)收集器,并且它能建立可預(yù)測(cè)的停頓時(shí)間模型。

下面補(bǔ)充一下關(guān)于內(nèi)存分配方面的東西:

淺談JAVA垃圾回收機(jī)制

對(duì)象的內(nèi)存分配,往大方向上講就是在堆上分配,對(duì)象主要分配在新生代的Eden Space和From Space,少數(shù)情況下會(huì)直接分配在老年代。如果新生代的Eden Space和From Space的空間不足,則會(huì)發(fā)起一次GC,如果進(jìn)行了GC之后,Eden Space和From Space能夠容納該對(duì)象就放在Eden Space和From Space。在GC的過(guò)程中,會(huì)將Eden Space和From Space中的存活對(duì)象移動(dòng)到To Space,然后將Eden Space和From Space進(jìn)行清理。如果在清理的過(guò)程中,To Space無(wú)法足夠來(lái)存儲(chǔ)某個(gè)對(duì)象,就會(huì)將該對(duì)象移動(dòng)到老年代中。在進(jìn)行了GC之后,使用的便是Eden space和To Space了,下次GC時(shí)會(huì)將存活對(duì)象復(fù)制到From Space,如此反復(fù)循環(huán)。當(dāng)對(duì)象在Survivor區(qū)躲過(guò)一次GC的話,其對(duì)象年齡便會(huì)加1,默認(rèn)情況下,如果對(duì)象年齡達(dá)到15歲,就會(huì)移動(dòng)到老年代中。

一般來(lái)說(shuō),大對(duì)象會(huì)被直接分配到老年代,所謂的大對(duì)象是指需要大量連續(xù)存儲(chǔ)空間的對(duì)象,最常見(jiàn)的一種大對(duì)象就是大數(shù)組,比如:
byte[] data = new byte[410241024]

這種一般會(huì)直接在老年代分配存儲(chǔ)空間。

當(dāng)然分配的規(guī)則并不是百分之百固定的,這要取決于當(dāng)前使用的是哪種垃圾收集器組合和JVM的相關(guān)參數(shù)。

看完這篇關(guān)于淺談JAVA垃圾回收機(jī)制的文章,如果覺(jué)得文章內(nèi)容寫(xiě)得不錯(cuò)的話,可以把它分享出去給更多人看到。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI