免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
深入理解JVM之垃圾回收詳解

一、 垃圾收集的意義

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

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

二、對象的判定

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

1、 引用計數(shù)法

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

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

2、 根搜索算法

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

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

三、引用的類型

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

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

強引用在實際應(yīng)用中非常常見,集合類中的clear方法就用到了強引用,下面看一下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,強引用仍然存在,避免在其他方法用到數(shù)組時重新的內(nèi)存分配。使用如clear方法中釋放內(nèi)存的方法對數(shù)組中存放的引用類型特別適用,這樣就可以及時釋放內(nèi)存。

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

軟引用主要應(yīng)用于內(nèi)存敏感的高速緩存,在android系統(tǒng)中經(jīng)常使用到。一般情況下,Android應(yīng)用會用到大量的默認圖片,這些圖片很多地方會用到。如果每次都去讀取圖片,由于讀取文件需要硬件操作,速度較慢,會導致性能較低。所以我們考慮將圖片緩存起來,需要的時候直接從內(nèi)存中讀取。但是,由于圖片占用內(nèi)存空間比較大,緩存很多圖片需要很多的內(nèi)存,就可能比較容易發(fā)生OutOfMemory異常。這時,我們可以考慮使用軟引用技術(shù)來避免這個問題發(fā)生。SoftReference可以解決oom的問題,每一個對象通過軟引用進行實例化,這個對象就以cache的形式保存起來,當再次調(diào)用這個對象時,那么直接通過軟引用中的get方法,就可以得到對象中中的資源數(shù)據(jù),這樣就沒必要再次進行讀取了,直接從cache中就可以讀取得到,當內(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) { // 強引用的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、弱引用也是用來描述非必需對象的,當JVM進行垃圾回收時,無論內(nèi)存是否充足,都會回收被弱引用關(guān)聯(lián)的對象。在jdk1.2后,用WeakReference類來實現(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進行垃圾回收 System.out.println(sr.get);//輸出null //如果存在強引用同時與之關(guān)聯(lián),則進行垃圾回收時也不會回收該對象 String str = new String (new String("弱引用")); sr = new WeakReference<String>(str); System.gc; //通知JVM的gc進行垃圾回收 System.out.println(sr.get); //輸出弱引用 } }4、虛引用和前面的軟引用、弱引用不同,它并不影響對象的生命周期。在java中用java.lang.ref.PhantomReference類表示。如果一個對象與虛引用關(guān)聯(lián),則跟沒有引用與之關(guān)聯(lián)一樣,在任何時候都可能被垃圾回收器回收。要注意的是,虛引用必須和引用隊列關(guān)聯(lián)使用,當垃圾回收器準備回收一個對象時,如果發(fā)現(xiàn)它還有虛引用,就會把這個虛引用加入到與之關(guān)聯(lián)的引用隊列中。程序可以通過判斷引用隊列中是否已經(jīng)加入了虛引用,來了解被引用的對象是否將要被垃圾回收。如果程序發(fā)現(xiàn)某個虛引用已經(jīng)被加入到引用隊列,那么就可以在所引用的對象的內(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、標記-清除算法(Mark-Sweep)

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

圖一、“標記-清除”算法示意圖

2、復制算法(Coping)

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

圖二、復制算法示意圖

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

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

圖三、“標記-整理”算法示意圖

4、分代收集算法

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

五、垃圾收集器

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

圖四、 HotSpot虛擬機的垃圾收集器

1、Serial收集器

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

圖五、Serial/SerialOld收集器運行示意圖

2、ParNew收集器

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

圖六、ParNew/SerialOld收集器運行示意圖

3、Parallel Scavenge收集器

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

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

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

4、Serial Old收集器

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

5、Parallel Old收集器

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

圖七、Parallel Scavenge/Parallel Old收集器運行示意圖

6、CMS收集器

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

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

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

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

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

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

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

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

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

圖八、CMS收集器運行示意圖

7、G1收集器

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

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

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

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

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

G1收集器運作大致可以分為以下幾個步驟:

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

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

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

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

圖九、G1收集器運行示意圖

本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
JDK5.0垃圾收集優(yōu)化之--Don‘t Pause
HotSpot 垃圾回收算法實現(xiàn) | 碼蜂筆記
JVM 專題二十一:垃圾回收(五)垃圾回收器 (二)
《深入理解JAVA虛擬機》——學習筆記
java - GC垃圾收集器詳解(二)
JVM合理理解大總結(jié)(一)_耐心閱讀
更多類似文章 >>
生活服務(wù)
分享 收藏 導長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服