自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Java虛擬機(jī)詳解----GC算法和種類

開發(fā) 后端 算法
在C/C++里是由程序猿自己去申請(qǐng)、管理和釋放內(nèi)存空間,因此沒有GC的概念。而在Java中,后臺(tái)專門有一個(gè)專門用于垃圾回收的線程來(lái)進(jìn)行監(jiān)控、掃描,自動(dòng)將一些無(wú)用的內(nèi)存進(jìn)行釋放,這就是垃圾收集的一個(gè)基本思想,目的在于防止由程序猿引入的人為的內(nèi)存泄露。

本文主要內(nèi)容:

  • GC的概念

  • GC算法

    引用計(jì)數(shù)法(無(wú)法解決循環(huán)引用的問(wèn)題,不被java采納)

      根搜索算法

      現(xiàn)代虛擬機(jī)中的垃圾搜集算法:

      標(biāo)記-清除

      復(fù)制算法(新生代)

      標(biāo)記-壓縮(老年代)

      分代收集

  • Stop-The-World

一、GC的概念:

  • GC:Garbage Collection 垃圾收集

  • 1960年 Lisp使用了GC

  • Java中,GC的對(duì)象是Java堆和方法區(qū)(即***區(qū))

我們接下來(lái)對(duì)上面的三句話進(jìn)行一一的解釋:

(1)GC:Garbage Collection 垃圾收集。這里所謂的垃圾指的是在系統(tǒng)運(yùn)行過(guò)程當(dāng)中所產(chǎn)生的一些無(wú)用的對(duì)象,這些對(duì)象占據(jù)著一定的內(nèi)存空間,如果長(zhǎng)期不被釋放,可能導(dǎo)致OOM。

在C/C++里是由程序猿自己去申請(qǐng)、管理和釋放內(nèi)存空間,因此沒有GC的概念。而在Java中,后臺(tái)專門有一個(gè)專門用于垃圾回收的線程來(lái)進(jìn)行監(jiān)控、掃描,自動(dòng)將一些無(wú)用的內(nèi)存進(jìn)行釋放,這就是垃圾收集的一個(gè)基本思想,目的在于防止由程序猿引入的人為的內(nèi)存泄露

(2)事實(shí)上,GC的歷史比Java久遠(yuǎn),1960年誕生于MIT的Lisp是***門真正使用內(nèi)存動(dòng)態(tài)分配和垃圾收集技術(shù)的語(yǔ)言。當(dāng)Lisp還在胚胎時(shí)期時(shí),人們就在思考GC需要完成的3件事情:

哪些內(nèi)存需要回收?

什么時(shí)候回收?

如何回收?

(3)內(nèi)存區(qū)域中的程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧這3個(gè)區(qū)域隨著線程而生,線程而滅;棧中的棧幀隨著方法的進(jìn)入和退出而有條不紊地執(zhí)行著出棧和入棧的操作,每個(gè)棧幀中分配多少內(nèi)存基本是在類結(jié)構(gòu)確定下來(lái)時(shí)就已知的。在這幾個(gè)區(qū)域不需要過(guò)多考慮回收的問(wèn)題,因?yàn)榉椒ńY(jié)束或者線程結(jié)束時(shí),內(nèi)存自然就跟著回收了

Java堆和方法區(qū)則不同,一個(gè)接口中的多個(gè)實(shí)現(xiàn)類需要的內(nèi)存可能不同,一個(gè)方法中的多個(gè)分支需要的內(nèi)存也可能不一樣,我們只有在程序處于運(yùn)行期間時(shí)才能知道會(huì)創(chuàng)建哪些對(duì)象,這部分內(nèi)存的分配和回收都是動(dòng)態(tài)的,GC關(guān)注的也是這部分內(nèi)存,后面的文章中如果涉及到“內(nèi)存”分配與回收也僅指著一部分內(nèi)存。

#p#

二、引用計(jì)數(shù)算法:(老牌垃圾回收算法。無(wú)法處理循環(huán)引用,沒有被Java采納)

1、引用計(jì)數(shù)算法的概念:

給對(duì)象中添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它時(shí),計(jì)數(shù)器值就加1;當(dāng)引用失效時(shí),計(jì)數(shù)器值就減1;任何時(shí)刻計(jì)數(shù)器為0的對(duì)象就是不可能再被使用的。

e6217360-0985-46e8-88fd-022f1fc0fba5

2、使用者舉例:

引用計(jì)數(shù)算法的實(shí)現(xiàn)簡(jiǎn)單,判定效率也高,大部分情況下是一個(gè)不錯(cuò)的算法。很多地方應(yīng)用到它。例如:

微軟公司的COM技術(shù):Computer Object Model

使用ActionScript3的FlashPlayer

Python

但是,主流的java虛擬機(jī)并沒有選用引用計(jì)數(shù)算法來(lái)管理內(nèi)存,其中最主要的原因是:它很難解決對(duì)象之間相互循環(huán)引用的問(wèn)題

3、引用計(jì)數(shù)算法的問(wèn)題:

  • 引用和去引用伴隨加法和減法,影響性能

  • 致命的缺陷:對(duì)于循環(huán)引用的對(duì)象無(wú)法進(jìn)行回收

1a489e67-e047-408f-a97e-4a141e6ab3b0

上面的3個(gè)圖中,對(duì)于最右邊的那張圖而言:循環(huán)引用的計(jì)數(shù)器都不為0,但是他們對(duì)于根對(duì)象都已經(jīng)不可達(dá)了,但是無(wú)法釋放。

循環(huán)引用的代碼舉例:

 

  1. public class Object { 
  2.  
  3.     Object field = null
  4.      
  5.     public static void main(String[] args) { 
  6.         Thread thread = new Thread(new Runnable() { 
  7.             public void run() { 
  8.                 Object objectA = new Object(); 
  9.                 Object objectB = new Object();//位置1 
  10.                 objectA.field = objectB; 
  11.                 objectB.field = objectA;//位置2 
  12.                 //to do something 
  13.                 objectA = null
  14.                 objectB = null;//位置3 
  15.             } 
  16.         }); 
  17.         thread.start(); 
  18.         while (true); 
  19.     } 
  20.      

 

上方代碼看起來(lái)有點(diǎn)刻意為之,但其實(shí)在實(shí)際編程過(guò)程當(dāng)中,是經(jīng)常出現(xiàn)的,比如兩個(gè)一對(duì)一關(guān)系的數(shù)據(jù)庫(kù)對(duì)象,各自保持著對(duì)方的引用。***一個(gè)***循環(huán)只是為了保持JVM不退出,沒什么實(shí)際意義。

代碼解釋:

代碼中標(biāo)注了1、2、3三個(gè)數(shù)字,當(dāng)位置1的語(yǔ)句執(zhí)行完以后,兩個(gè)對(duì)象的引用計(jì)數(shù)全部為1。當(dāng)位置2的語(yǔ)句執(zhí)行完以后,兩個(gè)對(duì)象的引用計(jì)數(shù)就全部變 成了2。當(dāng)位置3的語(yǔ)句執(zhí)行完以后,也就是將二者全部歸為空值以后,二者的引用計(jì)數(shù)仍然為1。根據(jù)引用計(jì)數(shù)算法的回收規(guī)則,引用計(jì)數(shù)沒有歸0的時(shí)候是不會(huì) 被回收的。

對(duì)于我們現(xiàn)在使用的GC來(lái)說(shuō),當(dāng)thread線程運(yùn)行結(jié)束后,會(huì)將objectA和objectB全部作為待回收的對(duì)象。而果我們的GC采用上面所說(shuō)的引用計(jì)數(shù)算法,則這兩個(gè)對(duì)象永遠(yuǎn)不會(huì)被回收,即便我們?cè)谑褂煤箫@示的將對(duì)象歸為空值也毫無(wú)作用。

#p#

三、根搜索算法:

1、根搜索算法的概念:

  由于引用計(jì)數(shù)算法的缺陷,所以JVM一般會(huì)采用一種新的算法,叫做根搜索算法。它的處理方式就是,設(shè)立若干種根對(duì)象,當(dāng)任何一個(gè)根對(duì)象到某一個(gè)對(duì)象均不可達(dá)時(shí),則認(rèn)為這個(gè)對(duì)象是可以被回收的。

7ab0f17b-13f7-4886-a24d-3813c2173891

如上圖所示,ObjectD和ObjectE是互相關(guān)聯(lián)的,但是由于GC roots到這兩個(gè)對(duì)象不可達(dá),所以最終D和E還是會(huì)被當(dāng)做GC的對(duì)象,上圖若是采用引用計(jì)數(shù)法,則A-E五個(gè)對(duì)象都不會(huì)被回收。

2、可達(dá)性分析:

 我們剛剛提到,設(shè)立若干種根對(duì)象,當(dāng)任何一個(gè)根對(duì)象到某一個(gè)對(duì)象均不可達(dá)時(shí),則認(rèn)為這個(gè)對(duì)象是可以被回收的。我們?cè)诤竺娼榻B標(biāo)記-清理算法/標(biāo)記整理算法時(shí),也會(huì)一直強(qiáng)調(diào)從根節(jié)點(diǎn)開始,對(duì)所有可達(dá)對(duì)象做一次標(biāo)記,那什么叫做可達(dá)呢?這里解釋如下:

可達(dá)性分析:

  從根(GC Roots)的對(duì)象作為起始點(diǎn),開始向下搜索,搜索所走過(guò)的路徑稱為引用鏈”,當(dāng)一個(gè)對(duì)象到GC Roots沒有任何引用鏈相連(用圖論的概念來(lái)講,就是從GC Roots到這個(gè)對(duì)象不可達(dá))時(shí),則證明此對(duì)象是不可用的。

3、根(GC Roots):

說(shuō)到GC roots(GC根),在JAVA語(yǔ)言中,可以當(dāng)做GC roots的對(duì)象有以下幾種:

1、棧(棧幀中的本地變量表)中引用的對(duì)象。

2、方法區(qū)中的靜態(tài)成員。

3、方法區(qū)中的常量引用的對(duì)象(全局變量)

4、本地方法棧中JNI(一般說(shuō)的Native方法)引用的對(duì)象。

注:***和第四種都是指的方法的本地變量表,第二種表達(dá)的意思比較清晰,第三種主要指的是聲明為final的常量值。

在根搜索算法的基礎(chǔ)上,現(xiàn)代虛擬機(jī)的實(shí)現(xiàn)當(dāng)中,垃圾搜集的算法主要有三種,分別是標(biāo)記-清除算法、復(fù)制算法、標(biāo)記-整理算法。這三種算法都擴(kuò)充了根搜索算法,不過(guò)它們理解起來(lái)還是非常好理解的。

#p#

四、標(biāo)記-清除算法:

1、標(biāo)記清除算法的概念:

標(biāo)記-清除算法是現(xiàn)代垃圾回收算法的思想基礎(chǔ)。標(biāo)記-清除算法將垃圾回收分為兩個(gè)階段:標(biāo)記階段和清除階段。一種可行的實(shí)現(xiàn)是,在標(biāo)記階段,首先通過(guò)根節(jié)點(diǎn),標(biāo)記所有從根節(jié)點(diǎn)開始的可達(dá)對(duì)象。因此,未被標(biāo)記的對(duì)象就是未被引用的垃圾對(duì)象;然后,在清除階段,清除所有未被標(biāo)記的對(duì)象。

7de44970-2e02-46a1-a5d0-0663b21906c6

2、標(biāo)記-清除算法詳解:

它的做法是當(dāng)堆中的有效內(nèi)存空間(available memory)被耗盡的時(shí)候,就會(huì)停止整個(gè)程序(也被成為stop the world),然后進(jìn)行兩項(xiàng)工作,***項(xiàng)則是標(biāo)記,第二項(xiàng)則是清除。

  • 標(biāo)記:標(biāo)記的過(guò)程其實(shí)就是,遍歷所有的GC Roots,然后將所有GC Roots可達(dá)的對(duì)象標(biāo)記為存活的對(duì)象。

  • 清除:清除的過(guò)程將遍歷堆中所有的對(duì)象,將沒有標(biāo)記的對(duì)象全部清除掉。

也就是說(shuō),就是當(dāng)程序運(yùn)行期間,若可以使用的內(nèi)存被耗盡的時(shí)候,GC線程就會(huì)被觸發(fā)并將程序暫停,隨后將依舊存活的對(duì)象標(biāo)記一遍,最終再將堆中所有沒被標(biāo)記的對(duì)象全部清除掉,接下來(lái)便讓程序恢復(fù)運(yùn)行。

來(lái)看下面這張圖:

47146934-c3a3-4976-991f-77e84ae008cc

上圖代表的是程序運(yùn)行期間所有對(duì)象的狀態(tài),它們的標(biāo)志位全部是0(也就是未標(biāo)記,以下默認(rèn)0就是未標(biāo)記,1為已標(biāo)記),假設(shè)這會(huì)兒有效內(nèi)存空間耗盡了,JVM將會(huì)停止應(yīng)用程序的運(yùn)行并開啟GC線程,然后開始進(jìn)行標(biāo)記工作,按照根搜索算法,標(biāo)記完以后,對(duì)象的狀態(tài)如下圖:

5cbf57ce-c83a-40d2-b58a-b37d3eee3803

上圖中可以看到,按照根搜索算法,所有從root對(duì)象可達(dá)的對(duì)象就被標(biāo)記為了存活的對(duì)象,此時(shí)已經(jīng)完成了***階段標(biāo)記。接下來(lái),就要執(zhí)行第二階段清除了,那么清除完以后,剩下的對(duì)象以及對(duì)象的狀態(tài)如下圖所示:

8654ed59-fc00-446d-8995-a02ab57cf213

上圖可以看到,沒有被標(biāo)記的對(duì)象將會(huì)回收清除掉,而被標(biāo)記的對(duì)象將會(huì)留下,并且會(huì)將標(biāo)記位重新歸0。接下來(lái)就不用說(shuō)了,喚醒停止的程序線程,讓程序繼續(xù)運(yùn)行即可。

疑問(wèn):為什么非要停止程序的運(yùn)行呢?

答:

這個(gè)其實(shí)也不難理解,假設(shè)我們的程序與GC線程是一起運(yùn)行的,各位試想這樣一種場(chǎng)景。

假設(shè)我們剛標(biāo)記完圖中最右邊的那個(gè)對(duì)象,暫且記為A,結(jié)果此時(shí)在程序當(dāng)中又new了一個(gè)新對(duì)象B,且A對(duì)象可以到達(dá)B對(duì)象。但是由于此時(shí)A對(duì)象已經(jīng) 標(biāo)記結(jié)束,B對(duì)象此時(shí)的標(biāo)記位依然是0,因?yàn)樗e(cuò)過(guò)了標(biāo)記階段。因此當(dāng)接下來(lái)輪到清除階段的時(shí)候,新對(duì)象B將會(huì)被苦逼的清除掉。如此一來(lái),不難想象結(jié) 果,GC線程將會(huì)導(dǎo)致程序無(wú)法正常工作。

上面的結(jié)果當(dāng)然令人無(wú)法接受,我們剛new了一個(gè)對(duì)象,結(jié)果經(jīng)過(guò)一次GC,忽然變成null了,這還怎么玩?

3、標(biāo)記-清除算法的缺點(diǎn):

(1)首先,它的缺點(diǎn)就是效率比較低(遞歸與全堆對(duì)象遍歷),導(dǎo)致stop the world的時(shí)間比較長(zhǎng),尤其對(duì)于交互式的應(yīng)用程序來(lái)說(shuō)簡(jiǎn)直是無(wú)法接受。試想一下,如果你玩一個(gè)網(wǎng)站,這個(gè)網(wǎng)站一個(gè)小時(shí)就掛五分鐘,你還玩嗎?

(2)第二點(diǎn)主要的缺點(diǎn),則是這種方式清理出來(lái)的空閑內(nèi)存是不連續(xù)的,這點(diǎn)不難理解,我們的死亡對(duì)象都是隨即的出現(xiàn)在內(nèi)存的各個(gè)角落的,現(xiàn)在把它們清除之后,內(nèi)存的布局自然會(huì)亂七八糟。而為了應(yīng)付這一點(diǎn),JVM就不得不維持一個(gè)內(nèi)存的空閑列表,這又是一種開銷。而且在分配數(shù)組對(duì)象的時(shí)候,尋找連續(xù)的內(nèi)存空間會(huì)不太好找。

#p#

五、復(fù)制算法:(新生代的GC)

復(fù)制算法的概念:

將原有的內(nèi)存空間分為兩塊,每次只使用其中一塊,在垃圾回收時(shí),將正在使用的內(nèi)存中的存活對(duì)象復(fù)制到未使用的內(nèi)存塊中,之后,清除正在使用的內(nèi)存塊中的所有對(duì)象,交換兩個(gè)內(nèi)存的角色,完成垃圾回收

  • 與標(biāo)記-清除算法相比,復(fù)制算法是一種相對(duì)高效的回收方法

  • 不適用于存活對(duì)象較多的場(chǎng)合,如老年代(復(fù)制算法適合做新生代的GC

ff1e1846-e49c-4663-aee1-7c63628f567c

  • 復(fù)制算法的***的問(wèn)題是:空間的浪費(fèi)

復(fù)制算法使得每次都只對(duì)整個(gè)半?yún)^(qū)進(jìn)行內(nèi)存回收,內(nèi)存分配時(shí)也就不用考慮內(nèi)存碎片等復(fù)雜情況,只要移動(dòng)堆頂指針,按順序分配內(nèi)存即可,實(shí)現(xiàn)簡(jiǎn)單,運(yùn)行高效。只是這種算法的代價(jià)是將內(nèi)存縮小為原來(lái)的一半,這個(gè)太要命了。

所以從以上描述不難看出,復(fù)制算法要想使用,最起碼對(duì)象的存活率要非常低才行,而且最重要的是,我們必須要克服50%內(nèi)存的浪費(fèi)。

現(xiàn)在的商業(yè)虛擬機(jī)都采用這種收集算法來(lái)回收新生代,新生代中的對(duì)象98%都是“朝生夕死”的,所以并不需要按照1:1的比例來(lái)劃分內(nèi)存空間,而是將內(nèi)存分為一塊比較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor。當(dāng)回收時(shí),將Eden和Survivor中還存活著的對(duì)象一次性地復(fù)制到另外一塊Survivor空間上,***清理掉Eden和剛才用過(guò)的Survivor空間。HotSpot虛擬機(jī)默認(rèn)Eden和Survivor的大小比例是8:1,也就是說(shuō),每次新生代中可用內(nèi)存空間為整個(gè)新生代容量的90%(80%+10%),只有10%的空間會(huì)被浪費(fèi)。

當(dāng)然,98%的對(duì)象可回收只是一般場(chǎng)景下的數(shù)據(jù),我們沒有辦法保證每次回收都只有不多于10%的對(duì)象存活,當(dāng)Survivor空間不夠用時(shí),需要依賴于老年代進(jìn)行分配擔(dān)保,所以大對(duì)象直接進(jìn)入老年代。整個(gè)過(guò)程如下圖所示:

7e1f6ed2-e0c4-45e4-b7db-b59c28e1ee9c

上圖中,綠色箭頭的位置代表的是大對(duì)象,大對(duì)象直接進(jìn)入老年代。

根據(jù)上面的復(fù)制算法,現(xiàn)在我們來(lái)看下面的這個(gè)gc日志的數(shù)字,就應(yīng)該能看得懂了吧:

6d59301f-f0c9-4fed-ba36-e66bc6574e8f

上方GC日志中,新生代的可用空間是13824K(eden區(qū)的12288K+from space的1536K)。而根據(jù)內(nèi)存的地址計(jì)算得知,新生代的總空間為15M,而這個(gè)15M的空間是 = 13824K +to space 的 1536K。

#p#

六、標(biāo)記-整理算法:(老年代的GC)

引入:

    如果在對(duì)象存活率較高時(shí)就要進(jìn)行較多的復(fù)制操作,效率將會(huì)變低。更關(guān)鍵的是,如果不想浪費(fèi)50%的空間,就需要有額外的空間進(jìn)行分配擔(dān)保,以應(yīng)對(duì)被使用的內(nèi)存中所有對(duì)象都100%存活的極端情況,所以在老年代一般不能直接選中這種算法。

概念:

標(biāo)記-壓縮算法適合用于存活對(duì)象較多的場(chǎng)合,如老年代。它在標(biāo)記-清除算法的基礎(chǔ)上做了一些優(yōu)化。和標(biāo)記-清除算法一樣,標(biāo)記-壓縮算法也首先需要從根節(jié)點(diǎn)開始,對(duì)所有可達(dá)對(duì)象做一次標(biāo)記;但之后,它并不簡(jiǎn)單的清理未標(biāo)記的對(duì)象,而是將所有的存活對(duì)象壓縮到內(nèi)存的一端;之后,清理邊界外所有的空間

cc79889a-0856-4018-92c3-c51108c9caea

  • 標(biāo)記:它的***個(gè)階段與標(biāo)記/清除算法是一模一樣的,均是遍歷GC Roots,然后將存活的對(duì)象標(biāo)記。

  • 整理:移動(dòng)所有存活的對(duì)象,且按照內(nèi)存地址次序依次排列,然后將末端內(nèi)存地址以后的內(nèi)存全部回收。因此,第二階段才稱為整理階段。

上圖中可以看到,標(biāo)記的存活對(duì)象將會(huì)被整理,按照內(nèi)存地址依次排列,而未被標(biāo)記的內(nèi)存會(huì)被清理掉。如此一來(lái),當(dāng)我們需要給新對(duì)象分配內(nèi)存時(shí),JVM只需要持有一個(gè)內(nèi)存的起始地址即可,這比維護(hù)一個(gè)空閑列表顯然少了許多開銷。

標(biāo)記/整理算法不僅可以彌補(bǔ)標(biāo)記/清除算法當(dāng)中,內(nèi)存區(qū)域分散的缺點(diǎn),也消除了復(fù)制算法當(dāng)中,內(nèi)存減半的高額代價(jià)。

  • 但是,標(biāo)記/整理算法唯一的缺點(diǎn)就是效率也不高。

不僅要標(biāo)記所有存活對(duì)象,還要整理所有存活對(duì)象的引用地址。從效率上來(lái)說(shuō),標(biāo)記/整理算法要低于復(fù)制算法。

標(biāo)記-清除算法、復(fù)制算法、標(biāo)記整理算法的總結(jié):

三個(gè)算法都基于根搜索算法去判斷一個(gè)對(duì)象是否應(yīng)該被回收,而支撐根搜索算法可以正常工作的理論依據(jù),就是語(yǔ)法中變量作用域的相關(guān)內(nèi)容。因此,要想防止內(nèi)存泄露,最根本的辦法就是掌握好變量作用域,而不應(yīng)該使用C/C++式內(nèi)存管理方式。

在GC線程開啟時(shí),或者說(shuō)GC過(guò)程開始時(shí),它們都要暫停應(yīng)用程序(stop the world)。

它們的區(qū)別如下:(>表示前者要優(yōu)于后者,=表示兩者效果一樣)

(1)效率復(fù)制算法>標(biāo)記/整理算法>標(biāo)記/清除算法(此處的效率只是簡(jiǎn)單的對(duì)比時(shí)間復(fù)雜度,實(shí)際情況不一定如此)。

(2)內(nèi)存整齊度:復(fù)制算法=標(biāo)記/整理算法>標(biāo)記/清除算法。

(3)內(nèi)存利用率:標(biāo)記/整理算法=標(biāo)記/清除算法>復(fù)制算法。

注1:可以看到標(biāo)記/清除算法是比較落后的算法了,但是后兩種算法卻是在此基礎(chǔ)上建立的。

注2:時(shí)間與空間不可兼得。

#p#

七、分代收集算法:(新生代的GC+老年代的GC)

當(dāng)前商業(yè)虛擬機(jī)的GC都是采用的“分代收集算法”,這并不是什么新的思想,只是根據(jù)對(duì)象的存活周期的不同將內(nèi)存劃分為幾塊兒。一般是把Java堆分為新生代和老年代:短命對(duì)象歸為新生代,長(zhǎng)命對(duì)象歸為老年代。

  • 少量對(duì)象存活,適合復(fù)制算法:在新生代中,每次GC時(shí)都發(fā)現(xiàn)有大批對(duì)象死去,只有少量存活,那就選用復(fù)制算法,只需要付出少量存活對(duì)象的復(fù)制成本就可以完成GC。

  • 大量對(duì)象存活,適合用標(biāo)記-清理/標(biāo)記-整理:在老年代中,因?yàn)?span style="color: #0000ff;">對(duì)象存活率高、沒有額外空間對(duì)他進(jìn)行分配擔(dān)保,就必須使用“標(biāo)記-清理”/“標(biāo)記-整理”算法進(jìn)行GC。

注:老年代的對(duì)象中,有一小部分是因?yàn)樵谛律厥諘r(shí),老年代做擔(dān)保,進(jìn)來(lái)的對(duì)象;絕大部分對(duì)象是因?yàn)楹芏啻蜧C都沒有被回收掉而進(jìn)入老年代

八、可觸及性:

所有的算法,需要能夠識(shí)別一個(gè)垃圾對(duì)象,因此需要給出一個(gè)可觸及性的定義。

可觸及的:

  從根節(jié)點(diǎn)可以觸及到這個(gè)對(duì)象。

      其實(shí)就是從根節(jié)點(diǎn)掃描,只要這個(gè)對(duì)象在引用鏈中,那就是可觸及的。

可復(fù)活的:

  一旦所有引用被釋放,就是可復(fù)活狀態(tài)

  因?yàn)樵趂inalize()中可能復(fù)活該對(duì)象

不可觸及的:

  在finalize()后,可能會(huì)進(jìn)入不可觸及狀態(tài)

  不可觸及的對(duì)象不可能復(fù)活

      要被回收。

finalize方法復(fù)活對(duì)象的代碼舉例:

 

  1. package test03; 
  2.  
  3. /** 
  4.  * Created by smyhvae on 2015/8/19. 
  5.  */ 
  6. public class CanReliveObj { 
  7.     public static CanReliveObj obj; 
  8.  
  9.     //當(dāng)執(zhí)行GC時(shí),會(huì)執(zhí)行finalize方法,并且只會(huì)執(zhí)行一次 
  10.     @Override 
  11.     protected void finalize() throws Throwable { 
  12.         super.finalize(); 
  13.         System.out.println("CanReliveObj finalize called"); 
  14.         obj = this;   //當(dāng)執(zhí)行GC時(shí),會(huì)執(zhí)行finalize方法,然后這一行代碼的作用是將null的object復(fù)活一下,然后變成了可觸及性 
  15.     } 
  16.  
  17.     @Override 
  18.     public String toString() { 
  19.         return "I am CanReliveObj"
  20.     } 
  21.  
  22.     public static void main(String[] args) throws 
  23.             InterruptedException { 
  24.         obj = new CanReliveObj(); 
  25.         obj = null;   //可復(fù)活 
  26.         System.out.println("***次gc"); 
  27.         System.gc(); 
  28.         Thread.sleep(1000); 
  29.         if (obj == null) { 
  30.             System.out.println("obj 是 null"); 
  31.         } else { 
  32.             System.out.println("obj 可用"); 
  33.         } 
  34.         obj = null;    //不可復(fù)活 
  35.         System.out.println("第二次gc"); 
  36.         System.gc(); 
  37.         Thread.sleep(1000); 
  38.         if (obj == null) { 
  39.             System.out.println("obj 是 null"); 
  40.         } else { 
  41.             System.out.println("obj 可用"); 
  42.         } 
  43.     } 

 

我們需要注意第14行的注釋。一開始,我們?cè)诘?5行將obj設(shè)置為null, 然后執(zhí)行一次GC,本以為obj會(huì)被回收掉,其實(shí)并沒有,因?yàn)镚C的時(shí)候會(huì)調(diào)用11行的finalize方法,然后obj在第14行被復(fù)活了。緊接著又在 第34行設(shè)置obj設(shè)置為null,然后執(zhí)行一次GC,此時(shí)obj就被回收掉了,因?yàn)閒inalize方法只會(huì)執(zhí)行一次。

31011217-d3a2-4e5b-9503-4f0b9bad9161

finalize方法的使用總結(jié):

  • 經(jīng)驗(yàn):避免使用finalize(),操作不慎可能導(dǎo)致錯(cuò)誤。

  • 優(yōu)先級(jí)低,何時(shí)被調(diào)用,不確定

何時(shí)發(fā)生GC不確定,自然也就不知道finalize方法什么時(shí)候執(zhí)行

  • 如果要使用finalize去釋放資源,我們可以使用try-catch-finally來(lái)替代它

#p#

九、Stop-The-World:

1、Stop-The-World概念:

  Java中一種全局暫停的現(xiàn)象。

全局停頓,所有Java代碼停止,native代碼可以執(zhí)行,但不能和JVM交互

多半情況下是由于GC引起

    少數(shù)情況下由其他情況下引起,如:Dump線程、死鎖檢查、堆Dump。

2、GC時(shí)為什么會(huì)有全局停頓?

    打個(gè)比方:類比在聚會(huì),突然GC要過(guò)來(lái)打掃房間,聚會(huì)時(shí)很亂,又有新的垃圾產(chǎn)生,房間永遠(yuǎn)打掃不干凈,只有讓大家停止活動(dòng)了,才能將房間打掃干凈。

    況且,如果沒有全局停頓,會(huì)給GC線程造成很大的負(fù)擔(dān),GC算法的難度也會(huì)增加,GC很難去判斷哪些是垃圾。

3、Stop-The-World的危害:

長(zhǎng)時(shí)間服務(wù)停止,沒有響應(yīng)

遇到HA系統(tǒng),可能引起主備切換,嚴(yán)重危害生產(chǎn)環(huán)境。

  備注:HA:High Available, 高可用性集群。

d07bb3ea-1235-41d5-9fb1-56b4087d1acf

比如上面的這主機(jī)和備機(jī):現(xiàn)在是主機(jī)在工作,此時(shí)如果主機(jī)正在GC造成長(zhǎng)時(shí)間停頓,那么備機(jī)就會(huì)監(jiān)測(cè)到主機(jī)沒有工作,于是備機(jī)開始工作了;但是主機(jī)不工作只是暫時(shí)的,當(dāng)GC結(jié)束之后,主機(jī)又開始工作了,那么這樣的話,主機(jī)和備機(jī)就同時(shí)工作了。主機(jī)和備機(jī)同時(shí)工作其實(shí)是非常危險(xiǎn)的,很有可能會(huì)導(dǎo)致應(yīng)用程序不一致、不能提供正常的服務(wù)等,進(jìn)而影響生產(chǎn)環(huán)境。

代碼舉例:

(1)打印日志的代碼:(每隔100ms打印一條)

 

  1. public static class PrintThread extends Thread{ 
  2.     public static final long starttime=System.currentTimeMillis(); 
  3.     @Override 
  4.     public void run(){ 
  5.         try
  6.             while(true){ 
  7.                 long t=System.currentTimeMillis()-starttime; 
  8.                 System.out.println("time:"+t); 
  9.                 Thread.sleep(100); 
  10.             } 
  11.         }catch(Exception e){ 
  12.  
  13.         } 
  14.     } 
  15. }

上方代碼中,是負(fù)責(zé)打印日志的代碼,每隔100ms打印一條,并計(jì)算打印的時(shí)間。

(2)工作線程的代碼:(工作線程,專門用來(lái)消耗內(nèi)存)

 

  1. public static class MyThread extends Thread{ 
  2.     HashMap<Long,byte[]> map=new HashMap<Long,byte[]>(); 
  3.     @Override 
  4.     public void run(){ 
  5.         try
  6.             while(true){ 
  7.                 if(map.size()*512/1024/1024>=450){   //如果map消耗的內(nèi)存消耗大于450時(shí),那就清理內(nèi)存 
  8.                     System.out.println("=====準(zhǔn)備清理=====:"+map.size()); 
  9.                     map.clear(); 
  10.                 } 
  11.  
  12.                 for(int i=0;i<1024;i++){ 
  13.                     map.put(System.nanoTime(), new byte[512]); 
  14.                 } 
  15.                 Thread.sleep(1); 
  16.             } 
  17.         }catch(Exception e){ 
  18.             e.printStackTrace(); 
  19.         } 
  20.     } 

 

 

然后,我們?cè)O(shè)置gc的參數(shù)為:

-Xmx512M -Xms512M -XX:+UseSerialGC -Xloggc:gc.log -XX:+PrintGCDetails -Xmn1m -XX:PretenureSizeThreshold=50 -XX:MaxTenuringThreshold=1

打印日志如下:

8a8de388-7989-47f2-a7e1-496487e4be57

上圖中,紅色字體代表的正在GC。按道理來(lái)說(shuō),應(yīng)該是每隔100ms會(huì)打印輸出一條日志,但是當(dāng)執(zhí)行GC的時(shí)候,會(huì)出現(xiàn)群居停頓的情況,導(dǎo)致沒有按時(shí)輸出。

【聲明】 

歡迎轉(zhuǎn)載,但請(qǐng)保留文章原始出處→_→ 

生命壹號(hào):http://www.cnblogs.com/smyhvae/

文章來(lái)源:http://www.cnblogs.com/smyhvae/p/4744233.html

聯(lián)系方式:smyhvae@163.com 

責(zé)任編輯:王雪燕 來(lái)源: 博客園
相關(guān)推薦

2020-01-06 10:58:18

JvmGC機(jī)制虛擬機(jī)

2020-05-08 16:55:48

Java虛擬機(jī)JVM

2023-02-20 14:24:56

AndroidDalvikART

2012-05-18 10:22:23

2010-07-26 09:02:38

2013-07-17 09:32:58

2009-12-11 14:00:09

VMware虛擬機(jī)

2009-08-07 11:46:57

JAVA虛擬機(jī)安裝設(shè)置

2024-03-29 11:42:21

Java虛擬機(jī)

2024-03-26 07:30:07

Java虛擬機(jī)源文件

2018-02-06 22:18:47

Java虛擬機(jī)面試

2009-06-04 16:27:39

Java虛擬機(jī)JVMGC

2024-04-03 13:49:00

Java虛擬機(jī)方法區(qū)

2010-10-13 10:21:37

物理機(jī)虛擬機(jī)遷移

2023-01-09 18:30:53

架構(gòu)JVM

2010-09-17 15:12:57

JVMJava虛擬機(jī)

2020-12-08 05:58:57

CPU虛擬化虛擬機(jī)

2018-10-25 09:04:56

Java虛擬機(jī)JVM

2009-12-16 14:33:52

Host訪問(wèn)虛擬機(jī)

2009-12-16 13:44:12

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)