摘要:就是說(shuō)引用計(jì)數(shù)法很難解決對(duì)象之間的相互引用問(wèn)題也無(wú)法通知回收器去及時(shí)回收它。因?yàn)榇嬖谶@種缺點(diǎn),所以現(xiàn)在的虛擬機(jī)基本上并不是通過(guò)引用計(jì)數(shù)法來(lái)判斷對(duì)象是否存活。
1、為什么要進(jìn)行垃圾回收?
每當(dāng)在我們寫(xiě)代碼的時(shí)候,不管是new一個(gè)對(duì)象,還是引用,還是填充數(shù)據(jù)到數(shù)組,都是要占用空間,那么如果不及時(shí)回收就會(huì)對(duì)系統(tǒng)的運(yùn)行產(chǎn)生影響。java和c 一個(gè)很大的區(qū)別就在于,java的垃圾回收主要是jvm去做,而c語(yǔ)言是自己去控制。雖然JAVA可以手動(dòng)的調(diào)用方法 system.gc 去手動(dòng)控制垃圾回收,但據(jù)說(shuō)達(dá)不到立馬回收的效果。c 語(yǔ)言則是要自己去申請(qǐng)一塊內(nèi)存空間malloc ,使用完成還需要手動(dòng)去釋放掉,如果沒(méi)有及時(shí)釋放,或者申請(qǐng)出現(xiàn)內(nèi)存過(guò)大等,會(huì)造成內(nèi)存溢出等異常,不過(guò)功底深厚的大牛都會(huì)做的比較牛逼,很好的去控制。
2、如何判斷對(duì)象生死?在內(nèi)存回收的過(guò)程中,首先要確定一點(diǎn),該對(duì)象是否應(yīng)該被回收,哪些還"存活",哪些已"死亡"。
引用計(jì)數(shù)法
當(dāng)一個(gè)對(duì)象在代碼中被引用,那么就會(huì)在這個(gè)對(duì)象的引用計(jì)數(shù)值 + 1,當(dāng)引用失效的時(shí)候,計(jì)數(shù)值則相應(yīng) - 1。但是如果兩個(gè)對(duì)象存在相互引用的情況,如下:
A a = new A(); B b = new B(); a.instance = b; b.instance = a;
虛擬機(jī)就無(wú)法去判斷這兩個(gè)對(duì)象是否需要被回收,因?yàn)楸舜说囊弥刀疾皇?0。就是說(shuō)引用計(jì)數(shù)法很難解決對(duì)象之間的相互引用問(wèn)題, 也無(wú)法通知GC回收器去及時(shí)回收它。因?yàn)榇嬖谶@種缺點(diǎn),所以現(xiàn)在的虛擬機(jī)基本上并不是通過(guò)引用計(jì)數(shù)法來(lái)判斷對(duì)象是否存活。那是通過(guò)什么方法? 請(qǐng)看官往下看。
可達(dá)性分析算法
現(xiàn)在主流的商用語(yǔ)言的視線中都是通過(guò)可達(dá)性分析來(lái)判斷對(duì)象是否存活,比如JAVA,C#等。這種方法基本思想 ——以 GC Roots的對(duì)象作為起點(diǎn)向下搜索,搜索走過(guò)的路徑被稱為"引用鏈",當(dāng)一個(gè)對(duì)象沒(méi)有任何引用鏈相連,那么這個(gè)對(duì)象就是不可用的。如下圖所示:
gc roots 是什么? 是滿足下面任意條件的某個(gè)對(duì)象。
虛擬機(jī)棧中reference對(duì)象;
方法區(qū)靜態(tài)屬性引用對(duì)象;
方法區(qū)常量引用對(duì)象;
本地方法棧 所謂的native方法 引用的對(duì)象。
Hotspot中的native方法引用Java對(duì)象用的是通過(guò)句柄(handle)來(lái)引用。HotSpot的JNI handle是放在若干不同的區(qū)域里的,但不會(huì)放在GC堆中。傳遞參數(shù)用的handle直接在棧上;local handle放在每個(gè)Java線程中的JNIHandleBlock里;global handle放在VM全局的JNIHandleBlock里。
關(guān)于什么是native方法請(qǐng)如下鏈接:https://segmentfault.com/n/13...
注:并不是不可達(dá)的對(duì)象就必須 "死",他們還是處于"緩刑", 真正要宣告一個(gè)對(duì)象死亡,需要經(jīng)過(guò)兩次標(biāo)記的過(guò)程:經(jīng)過(guò)可達(dá)性分析后對(duì)象沒(méi)有和GC Roots 連接的引用鏈,那么需要被標(biāo)記一次然后還需要經(jīng)過(guò)篩選(篩選條件:判斷該對(duì)象是否有必要執(zhí)行finalize()方法),如果對(duì)象已經(jīng)調(diào)用了或者沒(méi)有覆蓋finalize方法(finalize() 方法只會(huì)被執(zhí)行一次!),那么 虛擬機(jī)判定該對(duì)象是 "沒(méi)有必要執(zhí)行該方法"。
如果該對(duì)象有必要執(zhí)行finalize方法,那么對(duì)象會(huì)被放置在一個(gè)叫做F-Queue 的隊(duì)列之中,之后會(huì)由虛擬機(jī)自動(dòng)建立,由低優(yōu)先級(jí)的Finalize 方法去執(zhí)行。(執(zhí)行時(shí)只去觸發(fā)對(duì)象的finalize()方法,但是并不等待他運(yùn)行結(jié)束,防止有的對(duì)象finalize()進(jìn)行緩慢,或者死循環(huán),會(huì)導(dǎo)致隊(duì)列持續(xù)等待,進(jìn)而內(nèi)存回收系統(tǒng)崩潰。)稍后GC 會(huì)對(duì)F-Queue 隊(duì)列中的對(duì)象進(jìn)行第二次標(biāo)記,當(dāng)finalize 方法執(zhí)行后成功將對(duì)象連接到引用鏈上任何一個(gè)對(duì)象,那么這個(gè)對(duì)象就被拯救成功了,不然則go die!
什么是引用?Java中定義:如果reference類型的數(shù)據(jù)中存儲(chǔ)的數(shù)值代表的是另一塊內(nèi)存的起始地址,就稱是這塊內(nèi)存的一個(gè)引用。
強(qiáng)引用:類似于A a = new A(),只要引用在,就永遠(yuǎn)不會(huì)回收被引用的對(duì)象。
軟引用:描述有用但并非必須的獨(dú)享。在系統(tǒng)將要發(fā)生內(nèi)存溢出的時(shí)候,會(huì)將這部分對(duì)象回收。
弱引用:非必需對(duì)象引用,能生存到下次發(fā)生GC之前。
虛引用:一點(diǎn)用都沒(méi)。只有當(dāng)對(duì)象被垃圾回收器回收的時(shí)候會(huì)收到一個(gè)系統(tǒng)通知。
方法區(qū)回收在我看來(lái)方法區(qū)的內(nèi)存,回收起來(lái)并沒(méi)有新生代那么明顯。方法區(qū)大多存有類的描述信息,靜態(tài)變量,常量,方法等信息,這些大多是系統(tǒng)常用的,很少去回收,回收效率微乎其微。然而永久帶的方法回收主要分成兩部分,一種是常量的回收,另一種是類的回收。
常量的回收
相當(dāng)于這個(gè)常量已經(jīng)被廢棄掉了。例如:方法區(qū)的常量池中有一個(gè)字符串常量 "java", 當(dāng)系統(tǒng)中沒(méi)有一個(gè)String對(duì)象指向這個(gè)常量的值得時(shí)候,那么這個(gè)常量在發(fā)生GC的時(shí)候?qū)?huì)被回收。
類的回收
類的回收
相對(duì)于常量的回收會(huì)麻煩多,需要滿足下面三個(gè)條件才會(huì)被回收:
1、類中所有的對(duì)象都被回收,就是堆中不存在該類的任何的實(shí)例;
2、加載該類的classloader被回收;
3、該類對(duì)應(yīng)的java.lang.Class對(duì)象沒(méi)有在任何地方引用,或是通過(guò)反射機(jī)制訪問(wèn)不到該類。
注: 在大量使用反射,動(dòng)態(tài)代理,cGLib等ByteCode框架、動(dòng)態(tài)生成Jsp等頻繁定義classLoader的場(chǎng)景都需要虛擬機(jī)具備類的卸載功能,防止永久帶不會(huì)溢出。
3、垃圾回收算法標(biāo)記-清除算法
標(biāo)記-清除 算法是最基礎(chǔ)的算法,為什么呢?因?yàn)楹竺娴囊v的算法很多是從這個(gè)基本的算法改變其不足演變而來(lái)。
標(biāo)記-清除(Mark-Sweep) 算法正如其名字所說(shuō)由兩個(gè)部分來(lái)完成。首先,要對(duì)需要回收的對(duì)象進(jìn)行標(biāo)記,如何標(biāo)記上面已經(jīng)提過(guò)。然后,要對(duì)這些被標(biāo)記的對(duì)象進(jìn)行收集。
標(biāo)記:標(biāo)記的過(guò)程其實(shí)就是,遍歷所有的GC Roots,然后將所有GC Roots可達(dá)的對(duì)象標(biāo)記為存活的對(duì)象。
清除:清除的過(guò)程將遍歷堆中所有的對(duì)象,將沒(méi)有標(biāo)記的對(duì)象全部清除掉。
如下圖所示:
感覺(jué)清理完成之后,內(nèi)存零零散散,故該算法有以下兩個(gè)缺點(diǎn):
缺點(diǎn)一:被標(biāo)記的對(duì)象在內(nèi)存中分布很零散,回收之后可用內(nèi)存很零碎。如果當(dāng)一個(gè)進(jìn)程需要申請(qǐng)一塊連續(xù)的較大內(nèi)存時(shí),無(wú)法找到足夠的連續(xù)內(nèi)存,不得不提前觸發(fā)一次垃圾回收的動(dòng)作。
缺點(diǎn)二:標(biāo)記和清除的過(guò)程效率不高,而且在進(jìn)行GC的時(shí)候,需要停止應(yīng)用程序,這會(huì)導(dǎo)致用戶體驗(yàn)非常差,尤其對(duì)于交互式的應(yīng)用程序來(lái)說(shuō)簡(jiǎn)直是無(wú)法接受。
復(fù)制算法是對(duì)標(biāo)記-清除算法在回收后出現(xiàn)很多內(nèi)存碎片的一種改進(jìn),而且效率也有所提升。
復(fù)制算法(copying)將可用的內(nèi)存容量劃分成大小相等兩塊,每次只使用其中一塊,當(dāng)一塊內(nèi)存用完了,將還存活的對(duì)象復(fù)制到另一塊內(nèi)存中,然后將之前使用過(guò)的內(nèi)存空間全部清理掉。當(dāng)有效內(nèi)存空間耗盡時(shí),JVM將暫停程序運(yùn)行,開(kāi)啟復(fù)制算法GC線程 ,它會(huì)將活動(dòng)區(qū)間內(nèi)的存活對(duì)象,全部復(fù)制到空閑區(qū)間,且嚴(yán)格按照內(nèi)存地址依次排列,與此同時(shí),GC線程將更新存活對(duì)象的內(nèi)存引用地址指向新的內(nèi)存地址。 清空之前的內(nèi)存塊,減少了大量不連續(xù)的內(nèi)存碎片的產(chǎn)生,實(shí)現(xiàn)簡(jiǎn)單且運(yùn)行高效。
如下圖所示:
細(xì)心的讀者會(huì)發(fā)現(xiàn),這種算法有個(gè)很大的缺點(diǎn)——將可使用的內(nèi)存縮小成原來(lái)的一半,代價(jià)太大了!所以現(xiàn)在的虛擬機(jī)廠商都采用復(fù)制算法來(lái)回收新生代。研究表明 新生代對(duì)象98%都是 “朝生夕死” 所以不需要按照1:1劃分內(nèi)存空間,而是將內(nèi)存分為一塊較大的Eden 空間和兩塊較小的Survivor 空間。每次只是用 Eden 和 其中一塊 Survivor區(qū)域,另一塊Survivor 則用來(lái)當(dāng)作保留區(qū)。 那么這樣一來(lái),每次進(jìn)行回收的時(shí)候只需要將Eden 和 Survivor生還的對(duì)象復(fù)制到另一塊 Survivor空間,然后清理。HotShot虛擬機(jī) 新生代劃分比例默認(rèn) Eden:Survivor = 8:1。然而這樣分配內(nèi)存,有個(gè)問(wèn)題。當(dāng)作保留區(qū)的Survivor的內(nèi)存大小不夠承載 使用中的Eden和一塊Survivor區(qū)域的存活對(duì)象怎么辦?此時(shí)需要依賴其他內(nèi)存(老年代)進(jìn)行分配擔(dān)保。
分配擔(dān)保(Handle Promotion)——如果另一塊Survivor空間沒(méi)有足夠的空間存放上一次新生代收集下來(lái)的存活對(duì)象時(shí),這些對(duì)象直接通過(guò)分配擔(dān)保機(jī)制進(jìn)入老年代。
發(fā)生Minor GC(只回收Eden和Survivor區(qū))前,虛擬機(jī)檢查老年代最大可用連續(xù)空間是否大于新生代所有對(duì)象總空間。如果大于,那么Minor GC確保是安全的。如果不大于,則需要查看虛擬機(jī)HandlePromotionFailure參數(shù)設(shè)置,是否允許擔(dān)保失敗。若允許(true),會(huì)繼續(xù)檢查老年代最大連續(xù)可用空間是是否大于歷次晉升到老年代的對(duì)象平均大小。如果大于,會(huì)嘗試一次 Minor GC,盡管是有風(fēng)險(xiǎn)。(因?yàn)閮H僅是歷次晉升到老年代對(duì)象平均大小與老生代最大連續(xù)空間比較,如果內(nèi)存小無(wú)法容納,此時(shí)進(jìn)行Minor GC 會(huì)清理原本存活的對(duì)象所以是冒險(xiǎn)的,進(jìn)而需要進(jìn)行Full GC)如果小于或者Handle Promotion Failure不允許冒險(xiǎn),那么要進(jìn)行一次Full GC。
在jdk1.6 update24以后handle promotion failure 參數(shù)已經(jīng)不會(huì)影響到分配擔(dān)保的判定,具體代碼如下:
bool TenuredGeneration::promotion_attempt_is_safe(size_t max_promotion_in_bytes) const { // 老年代最大可用的連續(xù)空間 size_t available = max_contiguous_available(); // 每次晉升到老年代的平均大小 size_t av_promo = (size_t)gc_stats()->avg_promoted()->padded_average(); // 老年代可用空間是否大于平均晉升大小,或者老年代可用空間是否大于當(dāng)此GC時(shí)新生代所有對(duì)象容量 bool res = (available >= av_promo) || (available >=max_promotion_in_bytes); return res; }
如果老年代連續(xù)空閑空間大于歷屆晉升到老年代的對(duì)象的平均空間可以直接minor GC 否則 Full GC。
標(biāo)記-整理算法復(fù)制算法是需要將對(duì)象從從內(nèi)存一個(gè)區(qū)域復(fù)制到另一個(gè)區(qū)域,當(dāng)發(fā)現(xiàn)對(duì)象存活率很高的情況下,效率很低。而且在老生代的回收中,大多不采用復(fù)制算法,沒(méi)有額外的空間進(jìn)行分配擔(dān)保。
標(biāo)記-整理算法(Mark-Compact),過(guò)程和Mark-Sweep 方法過(guò)程一樣,也需要對(duì)對(duì)象進(jìn)行標(biāo)記,不過(guò)后續(xù)步驟不是直接對(duì)可回收對(duì)象進(jìn)行清理,算法分成兩個(gè)部分。
標(biāo)記:遍歷GC Roots,然后將存活的對(duì)象標(biāo)記。
整理:移動(dòng)所有存活的對(duì)象,且按照內(nèi)存地址次序依次排列,然后將末端內(nèi)存地址以后的內(nèi)存全部回收。
標(biāo)記-整理算法不僅可以彌補(bǔ)標(biāo)記-清除算法當(dāng)中,內(nèi)存區(qū)域分散的缺點(diǎn),也消除了復(fù)制算法當(dāng)中,內(nèi)存減半的高額代價(jià)。不過(guò)標(biāo)記-整理算法,效率是唯一缺點(diǎn)。它需要對(duì)存活對(duì)象進(jìn)行標(biāo)記,然后要整理存活對(duì)象內(nèi)存地址,相對(duì)于復(fù)制算法效率較低。
分代收集算法分代收集算法,當(dāng)前商用的虛擬機(jī)的垃圾收集大都采用這種算法。也不算是算法,只是把java 堆分成 新生代,老生代。分代之后,不同區(qū)域可以使用不同的收集算法。比如:
新生代 每次垃圾回收都會(huì)有大批對(duì)象死去,只有少量存活,那就采用復(fù)制-算法;
老生代 對(duì)象存活率高,也沒(méi)額外的空間對(duì)它進(jìn)行分配擔(dān)保,使用標(biāo)記-整理/清除算法會(huì)更好來(lái)回收。不同場(chǎng)景使用不同的算法更加有利于整體效率的提升。
JVM 在進(jìn)行GC 時(shí),并非每次都對(duì)上面三個(gè)內(nèi)存區(qū)域一起回收的,大部分時(shí)候回收的都是指新生代。因此GC按照回收的區(qū)域又分了兩種類型,一種是普通GC(minor GC),一種是全局GC(major GC or Full GC),它們所針對(duì)的區(qū)域如下。
普通GC(minor GC):只針對(duì)新生代區(qū)域的GC。
全局GC(major GC or Full GC):針對(duì)年老代的GC,偶爾伴隨對(duì)新生代的GC以及對(duì)永久代的GC。
注:由于年老代與永久代相對(duì)來(lái)說(shuō)GC效果不好,而且二者的內(nèi)存使用增長(zhǎng)速度也慢,因此一般情況下,需要經(jīng)過(guò)好幾次普通GC,才會(huì)觸發(fā)一次全局GC。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/66858.html
摘要:垃圾回收算法與垃圾回收器綜述我們常說(shuō)的垃圾回收算法可以分為兩部分對(duì)象的查找算法與真正的回收方法。串行垃圾回收器一次只使用一個(gè)線程進(jìn)行垃圾回收并行垃圾回收器一次將開(kāi)啟多個(gè)線程同時(shí)進(jìn)行垃圾回收。 垃圾回收算法與 JVM 垃圾回收器綜述歸納于筆者的 JVM 內(nèi)部原理與性能調(diào)優(yōu)系列文章,文中涉及的引用資料參考 Java 學(xué)習(xí)與實(shí)踐資料索引、JVM 資料索引。 showImg(https://s...
摘要:而使用虛擬機(jī)是實(shí)現(xiàn)這一特點(diǎn)的關(guān)鍵。每個(gè)字節(jié)碼指令都由一個(gè)字節(jié)的操作碼和附加的操作數(shù)組成。字節(jié)碼可以通過(guò)以下兩種方式轉(zhuǎn)換成合適的語(yǔ)言解釋器一條一條地讀取,解釋并執(zhí)行字節(jié)碼執(zhí)行,所以它可以很快地解釋字節(jié)碼,但是執(zhí)行起來(lái)會(huì)比較慢。 一、什么是JVM JVM是Java Virtual Machine(Java 虛擬機(jī))的縮寫(xiě),JVM是一種用于計(jì)算設(shè)備的規(guī)范,它是一個(gè)虛構(gòu)出來(lái)的計(jì)算機(jī),是通過(guò)在實(shí)...
垃圾回收(GC)是JVM的一大殺器,它使程序員可以更高效地專注于程序的開(kāi)發(fā)設(shè)計(jì),而不用過(guò)多地考慮對(duì)象的創(chuàng)建銷毀等操作。但是這并不是說(shuō)程序員不需要了解GC。GC只是Java編程中一項(xiàng)自動(dòng)化工具,任何一個(gè)工具都有它適用的范圍,當(dāng)超出它的范圍的時(shí)候,可能它將不是那么自動(dòng),而是需要人工去了解與適應(yīng)地適用。 擁有一定工作年限的程序員,在工作期間肯定會(huì)經(jīng)常碰到像內(nèi)存溢出、內(nèi)存泄露、高并發(fā)的場(chǎng)景。這時(shí)候在應(yīng)對(duì)這...
摘要:這個(gè)算法看似不錯(cuò)而且簡(jiǎn)單,不過(guò)存在這一個(gè)致命傷當(dāng)兩個(gè)對(duì)象互相引用的時(shí)候,就永遠(yuǎn)不會(huì)被回收于是引用計(jì)數(shù)算法就永遠(yuǎn)回收不了這兩個(gè)對(duì)象,下面介紹另一種算法。 前言 ? 如果要問(wèn)Java與其他編程語(yǔ)言最大的不同是什么,我第一個(gè)想到的一定就是Java所運(yùn)行的JVM所自帶的自動(dòng)垃圾回收機(jī)制,以下是我學(xué)習(xí)JVM垃圾回收機(jī)制整理的筆記,希望能對(duì)讀者有一些幫助。 哪些內(nèi)存需要回收?what? ? ...
摘要:在這種消耗很高的狀態(tài)下,應(yīng)用程序所有的線程都會(huì)掛起,暫停一切正常的工作,等待垃圾回收的完成。但是,因?yàn)榫€程切換和上下文轉(zhuǎn)換的消耗,會(huì)使得垃圾回收的總體成本上升,造成系統(tǒng)吞吐量的下降。 Java 垃圾回收(GC) 泛讀 文章地址: https://segmentfault.com/a/1190000008922319 0. 序言 帶著問(wèn)題去看待 垃圾回收(GC) 會(huì)比較好,一般來(lái)說(shuō)主要的...
閱讀 1644·2021-09-02 15:11
閱讀 1978·2019-08-30 14:04
閱讀 2566·2019-08-27 10:52
閱讀 1585·2019-08-26 11:52
閱讀 1207·2019-08-23 15:26
閱讀 2624·2019-08-23 15:09
閱讀 2607·2019-08-23 12:07
閱讀 2237·2019-08-22 18:41