日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

深入理解JVM之垃圾回收詳解

 jnstyle 2016-04-15

一、 垃圾收集的意義

C++中,對象所占的內(nèi)存在程序結(jié)束運(yùn)行之前一直被占用,在明確釋放之前不能分配給其它對象;而在Java中,當(dāng)沒有對象引用指向原先分配給某個對象的內(nèi)存時,該內(nèi)存便成為垃圾。垃圾收集器釋放丟棄對象所占的內(nèi)存空間,內(nèi)存會出現(xiàn)碎片。碎片是分配給對象的內(nèi)存塊之間的空閑內(nèi)存洞。碎片整理將所占用的堆內(nèi)存移到堆的一端,JVM將整理出的內(nèi)存分配給新的對象。

垃圾收集能自動釋放內(nèi)存空間,減輕編程的負(fù)擔(dān)。這使Java虛擬機(jī)具有一些優(yōu)點(diǎn)。首先,它能使編程效率提高。在沒有垃圾收集機(jī)制的時候,可能要花許多時間來解決一個難懂的存儲器問題。在用Java語言編程的時候,靠垃圾收集機(jī)制可大大縮短時間。其次是它保護(hù)程序的完整性, 垃圾收集是Java語言安全性策略的一個重要部份。垃圾收集的一個潛在的缺點(diǎn)是它的開銷影響程序性能。Java虛擬機(jī)必須追蹤運(yùn)行程序中有用的對象,而且最終釋放沒用的對象。這一個過程需要花費(fèi)處理器的時間。其次垃圾收集算法的不完備性,早先采用的某些垃圾收集算法就不能保證100%收集到所有的廢棄內(nèi)存。當(dāng)然隨著垃圾收集算法的不斷改進(jìn)以及軟硬件運(yùn)行效率的不斷提升,這些問題都可以迎刃而解。一般來說,Java開發(fā)人員可以不重視JVM中堆內(nèi)存的分配和垃圾處理收集,但是,充分理解Java的這一特性可以讓我們更有效地利用資源。同時要注意finalize方法是Java的缺省機(jī)制,有時為確保對象資源的明確釋放,可以編寫自己的finalize方法。

二、對象的判定

Java堆中存放著幾乎所有的對象實(shí)例,垃圾收集器對堆中的對象進(jìn)行回收前,要先確定這些對象是否還有用,判定對象是否為垃圾對象有如下算法:

1、 引用計(jì)數(shù)法

引用計(jì)數(shù)是垃圾收集器中的早期策略。在這種方法中,堆中每個對象(不是引用)都有一個引用計(jì)數(shù)。對于一個對象 A,只要有任何一個對象引用了 A,則A 的引用計(jì)數(shù)器就加 1,當(dāng)引用失效時,引用計(jì)數(shù)器就減 1。只要對象 A 的引用計(jì)數(shù)器的值為 0,則對象 A就不可能再被使用。

引用計(jì)數(shù)法實(shí)現(xiàn)簡單,判定效率也很高。但是這個算法有明顯的缺陷,對于循環(huán)引用的情況下,循環(huán)引用的對象就不會被回收。如A=B,B=A,此時,對象 A 和對象B 的引用計(jì)數(shù)器都不為 0。但是在系統(tǒng)中卻不存在任何第 3 個對象引用了 A 或 B。也就是說,A 和 B 是應(yīng)該被回收的垃圾對象,但由于垃圾對象間相互引用,從而使垃圾回收器無法識別,引起內(nèi)存泄漏。

2、 根搜索算法

這種算法的基本思路是通過一系列名為“GC Roots”的對象作為起始點(diǎn),從這些節(jié)點(diǎn)開始向下搜索,搜索所走過的路徑稱為引用鏈,當(dāng)一個對象到GC Roots沒有任何引用鏈相連時,就證明此對象是不可用的。在Java語言里,可作為GC Roots的對象包括下面幾種:

  • 虛擬機(jī)棧(棧幀中的本地變量表)中引用的對象。
  • 方法區(qū)中的類靜態(tài)屬性引用的對象。
  • 方法區(qū)中的常量引用的對象。
  • 本地方法棧中JNI(Native方法)的引用對象。

三、引用的類型

無論是通過引用計(jì)數(shù)算法判斷對象的引用數(shù)量,還是通過根搜索算法判斷對象的引用鏈?zhǔn)欠窨蛇_(dá),判斷對象是否存活都與“引用有關(guān)”。一般的引用類型分為強(qiáng)引用( Strong Reference)、軟引用( Soft Reference)、弱引用( Weak Reference)、虛引用( Phantom Reference)四種,這四種引用強(qiáng)度依次逐漸減弱。

1、強(qiáng)引用就是指在程序代碼之中普遍存在的,類似“Objectobj = new Object”這類的引用,只要強(qiáng)引用還存在,垃圾收集器永遠(yuǎn)不會回收掉被引用的對象。當(dāng)內(nèi)存空間不足,Java虛擬機(jī)寧愿拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具有強(qiáng)引用的對象來解決內(nèi)存不足的問題。如果不使用時,可以賦值obj=null,顯示的設(shè)置ob為null,則gc認(rèn)為該對象不存在引用,這時候就可以回收此對象。

強(qiáng)引用在實(shí)際應(yīng)用中非常常見,集合類中的clear方法就用到了強(qiáng)引用,下面看一下hashmap中clear方法的源代碼

transient Node<K,V> table; public void clear { Node<K,V> tab; modCount++; if ((tab = table) != null && size > 0) { size = 0; for (int i = 0; i < tab.length; ++i) tab[i] = null; } }

在HashMap類中定義了一個table數(shù)組,在調(diào)用clear方法清空數(shù)組時可以看到為每個數(shù)組內(nèi)容賦值為null。不同于table=null,強(qiáng)引用仍然存在,避免在其他方法用到數(shù)組時重新的內(nèi)存分配。使用如clear方法中釋放內(nèi)存的方法對數(shù)組中存放的引用類型特別適用,這樣就可以及時釋放內(nèi)存。

2、軟引用用來描述一些還有用,但并非必需的對象。對于軟引用關(guān)聯(lián)著的對象,如果內(nèi)存充足,則垃圾回收器不會回收該對象,如果內(nèi)存不夠了,就會回收這些對象的內(nèi)存。在JDK 1.2之后,提供了SoftReference類來實(shí)現(xiàn)軟引用。軟引用可用來實(shí)現(xiàn)內(nèi)存敏感的高速緩存。軟引用可以和一個引用隊(duì)列(ReferenceQueue)聯(lián)合使用,如果軟引用所引用的對象被垃圾回收器回收,Java虛擬機(jī)就會把這個軟引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。

軟引用主要應(yīng)用于內(nèi)存敏感的高速緩存,在android系統(tǒng)中經(jīng)常使用到。一般情況下,Android應(yīng)用會用到大量的默認(rèn)圖片,這些圖片很多地方會用到。如果每次都去讀取圖片,由于讀取文件需要硬件操作,速度較慢,會導(dǎo)致性能較低。所以我們考慮將圖片緩存起來,需要的時候直接從內(nèi)存中讀取。但是,由于圖片占用內(nèi)存空間比較大,緩存很多圖片需要很多的內(nèi)存,就可能比較容易發(fā)生OutOfMemory異常。這時,我們可以考慮使用軟引用技術(shù)來避免這個問題發(fā)生。SoftReference可以解決oom的問題,每一個對象通過軟引用進(jìn)行實(shí)例化,這個對象就以cache的形式保存起來,當(dāng)再次調(diào)用這個對象時,那么直接通過軟引用中的get方法,就可以得到對象中中的資源數(shù)據(jù),這樣就沒必要再次進(jìn)行讀取了,直接從cache中就可以讀取得到,當(dāng)內(nèi)存將要發(fā)生OOM的時候,GC會迅速把所有的軟引用清除,防止oom發(fā)生。

下面看一段簡單的代碼:

public class BitMapManager { private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>; //保存Bitmap的軟引用到HashMap public void saveBitmapToCache(String path) { // 強(qiáng)引用的Bitmap對象 Bitmap bitmap = BitmapFactory.decodeFile(path); // 軟引用的Bitmap對象 SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap); // 添加該對象到Map中使其緩存 imageCache.put(path, softBitmap); // 使用完后手動將位圖對象置null bitmap = null; } public Bitmap getBitmapByPath(String path) { // 從緩存中取軟引用的Bitmap對象 SoftReference<Bitmap> softBitmap = imageCache.get(path); // 判斷是否存在軟引用 if (softBitmap == null) { return null; } // 取出Bitmap對象,如果由于內(nèi)存不足Bitmap被回收,將取得空 Bitmap bitmap = softBitmap.get; return bitmap; } }

3、弱引用也是用來描述非必需對象的,當(dāng)JVM進(jìn)行垃圾回收時,無論內(nèi)存是否充足,都會回收被弱引用關(guān)聯(lián)的對象。在jdk1.2后,用WeakReference類來實(shí)現(xiàn)弱引用。弱引用與軟引用的區(qū)別在于:只具有弱引用的對象擁有更短暫的生命周期。下面看一個簡單例子:

import java.lang.ref.WeakReference; public class WeakReferenceTest { public static void main(String args) { WeakReference<String> sr ; sr= new WeakReference<String>(new String("弱引用")); System.out.println(sr.get);//輸出弱引用 System.gc; //通知JVM的gc進(jìn)行垃圾回收 System.out.println(sr.get);//輸出null //如果存在強(qiáng)引用同時與之關(guān)聯(lián),則進(jìn)行垃圾回收時也不會回收該對象 String str = new String (new String("弱引用")); sr = new WeakReference<String>(str); System.gc; //通知JVM的gc進(jìn)行垃圾回收 System.out.println(sr.get); //輸出弱引用 } }4、虛引用和前面的軟引用、弱引用不同,它并不影響對象的生命周期。在java中用java.lang.ref.PhantomReference類表示。如果一個對象與虛引用關(guān)聯(lián),則跟沒有引用與之關(guān)聯(lián)一樣,在任何時候都可能被垃圾回收器回收。要注意的是,虛引用必須和引用隊(duì)列關(guān)聯(lián)使用,當(dāng)垃圾回收器準(zhǔn)備回收一個對象時,如果發(fā)現(xiàn)它還有虛引用,就會把這個虛引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。程序可以通過判斷引用隊(duì)列中是否已經(jīng)加入了虛引用,來了解被引用的對象是否將要被垃圾回收。如果程序發(fā)現(xiàn)某個虛引用已經(jīng)被加入到引用隊(duì)列,那么就可以在所引用的對象的內(nèi)存被回收之前采取必要的行動。下面看一個簡單的例子:import java.lang.ref.PhantomReference; import java.lang.ref.ReferenceQueue; public class PhantomReferenceTest { public static void main(String args) { ReferenceQueue<String> queue = new ReferenceQueue<String>; PhantomReference<String> pr = new PhantomReference<String>(new String("虛引用"), queue); System.out.println(pr.get);//輸出null } }

四、垃圾收集算法

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

標(biāo)記-清除算法分為標(biāo)記和清除兩個階段:首先標(biāo)記出所有需要回收的對象,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對象,標(biāo)記過程其實(shí)就是根搜索算法判斷對象是否存活。該算法主要不足有兩個:一個是效率問題,標(biāo)記和清除兩個過程的效率都不高;另一個是空間問題,標(biāo)記清除之后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片,空間碎片太多可能會導(dǎo)致以后在程序運(yùn)行過程中需要分配較大的對象時,無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動作。標(biāo)記-清除算法的執(zhí)行過程如下圖所示:

圖一、“標(biāo)記-清除”算法示意圖

2、復(fù)制算法(Coping)

復(fù)制算法是把內(nèi)存分成大小相等的兩塊,每次使用其中一塊,當(dāng)垃圾回收的時候,把存活的對象復(fù)制到另一塊上,然后把這塊內(nèi)存整個清理掉。這樣使得每次都是對整個半?yún)^(qū)進(jìn)行內(nèi)存回收,內(nèi)存分配時就不用考慮內(nèi)存碎片等復(fù)雜情況,實(shí)現(xiàn)簡單,運(yùn)行高效。這種方法適用于短生存期的對象,持續(xù)復(fù)制長生存期的對象則導(dǎo)致效率降低。復(fù)制算法的執(zhí)行過程如下圖所示:

圖二、復(fù)制算法示意圖

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

復(fù)制算法在對象存活率較高時就要進(jìn)行較多的復(fù)制操作,效率將會降低。老年代更常見的情況是大部分對象都是存活對象。如果依然使用復(fù)制算法,由于存活的對象較多,復(fù)制的成本也將很高。標(biāo)記-整理算法是一種老年代的回收算法,該算法與標(biāo)記-清除算法的標(biāo)記過程一樣,但是之后不是直接對可回收對象進(jìn)行清理,而是讓所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內(nèi)存。這種方法既避免了碎片的產(chǎn)生,又不需要兩塊相同的內(nèi)存空間,其性價比比較高。該算法示意圖如下圖所示:

圖三、“標(biāo)記-整理”算法示意圖

4、分代收集算法

根據(jù)垃圾回收對象的特性,不同階段最優(yōu)的方式是使用合適的算法用于本階段的垃圾回收,分代算法即是基于這種思想,它將內(nèi)存區(qū)間根據(jù)對象的特點(diǎn)分成幾塊,根據(jù)每塊內(nèi)存區(qū)間的特點(diǎn),使用不同的回收算法,以提高垃圾回收的效率。一般把java堆分為新生代和老年代,新生代采用復(fù)制算法,老年代采用標(biāo)記-整理算法。

五、垃圾收集器

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

圖四、 HotSpot虛擬機(jī)的垃圾收集器

1、Serial收集器

這個收集器是一個單線程收集器,使用復(fù)制收集算法,收集時會暫停所有工作線程,直到收集結(jié)束,虛擬機(jī)運(yùn)行在Client模式時的默認(rèn)新生代收集器。優(yōu)點(diǎn)是:簡單高效(與其他收集器的單線程相比),對于限定單個CPU的環(huán)境來說,Serial收集器沒有現(xiàn)成交互的開銷,做垃圾收集可以獲得最高的單線程收集效率。如下圖:

圖五、Serial/SerialOld收集器運(yùn)行示意圖

2、ParNew收集器

ParNew收集器其實(shí)就是Serial收集器的多線程版本,除了使用多條線程進(jìn)行垃圾收集之外,其余行為包括算法、STW、對象分配規(guī)則、回收策略等都與Serial收集器一樣。ParNew收集器是許多運(yùn)行在server模式下的虛擬機(jī)中首選的新生代收集器,一個重要原因是在除了serial收集器外,目前只有它能與CMS收集器配合使用。ParNew收集器在單CPU環(huán)境中不比Serial效果好,甚至可能更差,兩個CPU也不一定跑的過,但隨著CPU數(shù)量的增加,性能會逐步增加。ParNew收集器的工作過程如下:

圖六、ParNew/SerialOld收集器運(yùn)行示意圖

3、Parallel Scavenge收集器

ParallelScavenge收集器是一個新生代收集器,它是使用復(fù)制算法的并行多線程的收集器。

ParallelScavenge的特點(diǎn)是它的關(guān)注點(diǎn)與其他收集器不同,CMS等收集器的關(guān)注點(diǎn)盡可能地縮短垃圾收集時用戶線程的停頓時間,而Parallel Scavenge收集器的目標(biāo)則是達(dá)到一個可控制的吞吐量(Throughput)。所謂吞吐量就是CPU用于運(yùn)行用戶代碼時間與CPU總消耗時間的比值。吞吐量=運(yùn)行用戶代碼時間/運(yùn)行用戶代碼時間+垃圾收集時間。

高吞吐量和停頓時間短的策略相比,主要強(qiáng)調(diào)高效率地利用CPU時間,任務(wù)更快完成,適用于后臺運(yùn)算而不需要太多交互的任務(wù);而后者強(qiáng)調(diào)用戶交互體驗(yàn)。

4、Serial Old收集器

單線程收集器,是Serial收集器老年代版本,使用“標(biāo)記-整理”算法,主要用在client模式下,如果在Server模式下,它主要有兩大用途:一種用途是在JDK1.5以及之前的版本中與Parallel Scavenge收集器搭配使用;另一用途是作為CMS收集器的后備預(yù)案,在并發(fā)手機(jī)發(fā)生CMF時使用。

5、Parallel Old收集器

Parallel Old是ParallelScavenge收集器的老年代版本,使用多線程和“標(biāo)記-整理”算法。Parallel Old收集器的工作過程如下圖:

圖七、Parallel Scavenge/Parallel Old收集器運(yùn)行示意圖

6、CMS收集器

CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標(biāo)的收集器。CMS收集器是基于“標(biāo)記-清除”算法實(shí)現(xiàn)的,整個收集過程大致分為4個步驟:

(1)初始標(biāo)記(CMS initial mark):標(biāo)記GC Roots能直接關(guān)聯(lián)到的對象,速度很快。

(2)并發(fā)標(biāo)記(CMS concurrent mark):進(jìn)行GC ROOTS根搜索算法階段,會判定對象是否存活。

(3)重新標(biāo)記(CMS remark):修正并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)行而導(dǎo)致標(biāo)記發(fā)生改變的那一部分對象的標(biāo)記記錄。

其中初始標(biāo)記和重新標(biāo)記兩個階段仍然需要Stop-The-World,整個過程中耗時最長的并發(fā)標(biāo)記和并發(fā)清除過程中收集器都可以和用戶線程一起工作。所以整體來說,CMS收集器的內(nèi)存回收過程是與用戶線程一起并發(fā)執(zhí)行的。

CMS收集器的優(yōu)點(diǎn):并發(fā)收集、低停頓,但是CMS還遠(yuǎn)遠(yuǎn)達(dá)不到完美,器主要有三個顯著缺點(diǎn):

(1)CMS收集器對CPU資源非常敏感。在并發(fā)階段,雖然不會導(dǎo)致用戶線程停頓,但是會占用CPU資源而導(dǎo)致引用程序變慢,總吞吐量下降。CMS默認(rèn)啟動的回收線程數(shù)是:(CPU數(shù)量+3) / 4。

(2)CMS收集器無法處理浮動垃圾,可能出現(xiàn)“Concurrent Mode Failure“,失敗后而導(dǎo)致另一次Full GC的產(chǎn)生。

(3)最后一個缺點(diǎn),CMS是基于“標(biāo)記-清除”算法實(shí)現(xiàn)的收集器,使用“標(biāo)記-清除”算法收集后,會產(chǎn)生大量碎片??臻g碎片太多時,將會給對象分配帶來很多麻煩,比如說大對象,內(nèi)存空間找不到連續(xù)的空間來分配不得不提前觸發(fā)一次Full GC。

圖八、CMS收集器運(yùn)行示意圖

7、G1收集器

G1收集器是一款面向服務(wù)端應(yīng)用的垃圾收集器,用于替換CMS收集器。與其他GC收集器相比,G1具有以下幾個特點(diǎn):

(1)并行與并發(fā):充分利用多CPU、多核環(huán)境下的硬件優(yōu)勢,使用多個CPU來縮短Stop-The-World停頓時間,在收集過程中用并發(fā)的方式讓Java線程繼續(xù)執(zhí)行。

(2)分代收集:仍然有分代的概念,不需要其他收集器配合能獨(dú)立管理整個GC堆,能夠采用不同的方式去處理新創(chuàng)建的對象和已經(jīng)存活了一段時間、熬過多次GC的就對象以獲得更好的收集效果。

(3)空間整合:G1從整體看,是基于“標(biāo)記-整理”算法實(shí)現(xiàn)的,從局部(兩個Region之間)看是基于“復(fù)制”算法的。在運(yùn)行期間不會產(chǎn)生內(nèi)存碎片,有利于程序長時間運(yùn)行分配大對象時不會因?yàn)闊o法找到連續(xù)內(nèi)存而提前出發(fā)下一次GC。

(4)可預(yù)測的停頓:G1除了追求低停頓外,還能建立可預(yù)測的停頓時間模型。

G1收集器運(yùn)作大致可以分為以下幾個步驟:

(1)初始標(biāo)記:只標(biāo)記GC Roots能直接關(guān)聯(lián)到的對象,并且修改TAMS(Next Topat Mark Start)值,讓下一階段用戶程序并發(fā)運(yùn)行時,能在正確可用的Region中創(chuàng)建新對象。此階段需要停頓用戶線程。

(2)并發(fā)標(biāo)記:從GC Roots開始對堆中對象進(jìn)行可達(dá)性分析,找出存活對象;耗時較長,可與用戶線程并發(fā)執(zhí)行。

(3)最終標(biāo)記:修正在并發(fā)標(biāo)記期間有變動的標(biāo)記記錄,這階段需要停頓線程,可以并行執(zhí)行。

(4)篩選回收:對各個Region的回收價值和成本進(jìn)行排序,根據(jù)用戶期望的GC停頓時間制定回收計(jì)劃,進(jìn)行垃圾回收。

圖九、G1收集器運(yùn)行示意圖

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多