溫馨提示×

溫馨提示×

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

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

Java垃圾回收機(jī)制簡述

發(fā)布時間:2020-08-22 19:50:29 來源:腳本之家 閱讀:127 作者:海子 欄目:編程語言

說到垃圾回收(Garbage Collection,GC),很多人就會自然而然地把它和Java聯(lián)系起來。在Java中,程序員不需要去關(guān)心內(nèi)存動態(tài)分配和垃圾回收的問題,這一切都交給了JVM來處理。

顧名思義,垃圾回收就是釋放垃圾占用的空間,那么在Java中,什么樣的對象會被認(rèn)定為“垃圾”?那么當(dāng)一些對象被確定為垃圾之后,采用什么樣的策略來進(jìn)行回收(釋放空間)?在目前的商業(yè)虛擬機(jī)中,有哪些典型的垃圾收集器?下面我們就來逐一探討這些問題。以下是本文的目錄大綱:

如何確定某個對象是“垃圾”?
典型的垃圾收集算法
典型的垃圾收集器

一.如何確定某個對象是“垃圾”?

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

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

  這種方式的特點(diǎn)是實(shí)現(xiàn)簡單,而且效率較高,但是它無法解決循環(huán)引用的問題,因此在Java中并沒有采用這種方式(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,也就是說object1和object2指向的對象已經(jīng)不可能再被訪問,但是由于它們互相引用對方,導(dǎo)致它們的引用計(jì)數(shù)都不為0,那么垃圾收集器就永遠(yuǎn)不會回收它們。

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

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

下面來看個例子:

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

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

再看一個例子:

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

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

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

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

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

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

void fun() {

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

循環(huán)每執(zhí)行完一次,生成的Object對象都會成為可回收的對象。

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

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

二.典型的垃圾收集算法

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

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

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

Java垃圾回收機(jī)制簡述

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

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

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

Java垃圾回收機(jī)制簡述

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

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

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

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

Java垃圾回收機(jī)制簡述

4.Generational Collection(分代收集)算法

  分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根據(jù)對象存活的生命周期將內(nèi)存劃分為若干個不同的區(qū)域。一般情況下將堆區(qū)劃分為老年代(Tenured Generation)和新生代(Young Generation),老年代的特點(diǎn)是每次垃圾收集時只有少量對象需要被回收,而新生代的特點(diǎn)是每次垃圾回收時都有大量的對象需要被回收,那么就可以根據(jù)不同代的特點(diǎn)采取最適合的收集算法。

  目前大部分垃圾收集器對于新生代都采取Copying算法,因?yàn)樾律忻看卫厥斩家厥沾蟛糠謱ο?,也就是說需要復(fù)制的操作次數(shù)較少,但是實(shí)際中并不是按照1:1的比例來劃分新生代的空間的。一般來說是將新生代劃分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden空間和其中的一塊Survivor空間,當(dāng)進(jìn)行回收時,將Eden和Survivor中還存活的對象復(fù)制到另一塊Survivor空間中,然后清理掉Eden和剛才使用過的Survivor空間。

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

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

三.典型的垃圾收集器

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

1.Serial/Serial Old

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

2.ParNew

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

3.Parallel Scavenge

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

4.Parallel Old

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

5.CMS

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

6.G1

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

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

Java垃圾回收機(jī)制簡述

  對象的內(nèi)存分配,往大方向上講就是在堆上分配,對象主要分配在新生代的Eden Space和From Space,少數(shù)情況下會直接分配在老年代。如果新生代的Eden Space和From Space的空間不足,則會發(fā)起一次GC,如果進(jìn)行了GC之后,Eden Space和From Space能夠容納該對象就放在Eden Space和From Space。

     在GC的過程中,會將Eden Space和From  Space中的存活對象移動到To Space,然后將Eden Space和From Space進(jìn)行清理。如果在清理的過程中,To Space無法足夠來存儲某個對象,就會將該對象移動到老年代中。在進(jìn)行了GC之后,使用的便是Eden space和To Space了,下次GC時會將存活對象復(fù)制到From Space,如此反復(fù)循環(huán)。當(dāng)對象在Survivor區(qū)躲過一次GC的話,其對象年齡便會加1,默認(rèn)情況下,如果對象年齡達(dá)到15歲,就會移動到老年代中。

  一般來說,大對象會被直接分配到老年代,所謂的大對象是指需要大量連續(xù)存儲空間的對象,最常見的一種大對象就是大數(shù)組,比如:

byte[] data = new byte[4*1024*1024]

  這種一般會直接在老年代分配存儲空間。

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

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。

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

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

AI