一般情況下java開(kāi)發(fā)進(jìn)階的標(biāo)志:1.將設(shè)計(jì)模式合理應(yīng)用到開(kāi)發(fā)中;2.良好的代碼編寫(xiě)風(fēng)格;3.熟練掌握自動(dòng)化測(cè)試技術(shù);4.熟悉代碼重構(gòu)的過(guò)程和方法;5.掌握多線程開(kāi)發(fā);6.對(duì)于虛擬機(jī)(JVM)機(jī)制的深入理解。所以我在面試高級(jí)開(kāi)發(fā)人員是一般會(huì)圍繞這幾個(gè)問(wèn)題來(lái)提問(wèn)。 之前已經(jīng)有了一篇介紹多線程開(kāi)發(fā)的文章,由于篇幅有限,這篇將著重于介紹Java垃圾回收(Garbage Collection,GC)機(jī)制。 習(xí)慣問(wèn)為什么是一個(gè)好習(xí)慣,下面就先介紹為什么要了解垃圾回收機(jī)制。
有不少人可能覺(jué)得java有自動(dòng)垃圾回收機(jī)制,一切聽(tīng)JVM的安排,我們完全可以不用關(guān)心這個(gè)問(wèn)題, 確實(shí)Java的GC已經(jīng)有了相當(dāng)長(zhǎng)的時(shí)間了,而且也確實(shí)日趨完善,在大部分情況下是可靠的,但在實(shí)際工作中還是可能會(huì)出現(xiàn)垃圾無(wú)法及時(shí)自動(dòng)回收導(dǎo)致的OOM問(wèn)題或Stop The World問(wèn)題,而且這種問(wèn)題一般都很難定位和解決。通過(guò)了解Java垃圾回收機(jī)制可以幫助我們更好的防范于未然和解決各種OOM問(wèn)題。 我們都知道Java會(huì)為我們自動(dòng)動(dòng)態(tài)分配內(nèi)存和自動(dòng)清理內(nèi)存,當(dāng)初幼稚的我再也不用擔(dān)心內(nèi)存溢出了,好吧那是后話,我們首先要回想一下什么樣的數(shù)據(jù)或內(nèi)存會(huì)被JVM自動(dòng)回收呢,JVM有沒(méi)有可能把我們要用的東西清理了呢,再進(jìn)一步要是我們?nèi)?shí)現(xiàn)GC的話我們會(huì)怎么做呢? 下面我們就開(kāi)始介紹什么樣的數(shù)據(jù)會(huì)被自動(dòng)清理。
在主流的商用語(yǔ)言的主流實(shí)現(xiàn)中確定內(nèi)存可否被回收都是通過(guò)可達(dá)性分析判定對(duì)象是否存活的。該算法的基本思想是通過(guò)一系列的“GC Roots”的對(duì)象作為起始點(diǎn),從這些起點(diǎn)開(kāi)始向下搜索,搜索所走過(guò)的路徑被稱(chēng)為引用連,當(dāng)一個(gè)對(duì)象到GC Roots沒(méi)有任何引用鏈相連時(shí),則證明此對(duì)象是不可用的。 需要注意的是GC Roots并不是唯一的節(jié)點(diǎn),其可以包括下面幾種來(lái)源: - 虛擬機(jī)棧中引用的對(duì)象 - 方法區(qū)中靜態(tài)屬性引用的對(duì)象 - 方法區(qū)中常量引用的對(duì)象 - 本地方法中JNI引用的對(duì)象 上面中的引用又可以分為強(qiáng)引用、軟引用、弱引用和虛引用; 強(qiáng)引用最為常見(jiàn),Object obj=new Object()就是強(qiáng)引用,只要還存在垃圾回收器就不會(huì)回收被引用的對(duì)象。 軟引用是描述一些還有用但并非必須的對(duì)象。對(duì)于軟引用關(guān)聯(lián)著的對(duì)象,在系統(tǒng)要發(fā)生OOM之前,將會(huì)把這些對(duì)象列進(jìn)回收的范圍進(jìn)行第二次回收。 弱引用也是描述非必需的對(duì)象,但它的強(qiáng)度還會(huì)再弱一些,被弱引用關(guān)聯(lián)的對(duì)象只能生存到下次GC之前。 虛引用也稱(chēng)為幽靈引用或幻影引用。一個(gè)對(duì)象是否有虛引用的存在并不影響其是否會(huì)被回收,區(qū)別只是如果有虛引用則會(huì)在它被回收時(shí)收到一個(gè)系統(tǒng)的通知。
還有一個(gè)問(wèn)題就算一個(gè)對(duì)象已經(jīng)被判定為“不可達(dá)”也不一定會(huì)被回收,特例就是若這個(gè)對(duì)象有finalize()方法,并且這個(gè)方法中又重新與”GC Roots”建立了聯(lián)系則這個(gè)對(duì)象就能死里逃生,但需要注意的是finalize()方法只能運(yùn)行一次,如果被判定為兩次則必死無(wú)疑。
從上面的判定標(biāo)準(zhǔn)可以看到,JVM是異常謹(jǐn)慎的,幾乎不可能會(huì)將有用的數(shù)據(jù)清理掉。
該算法是最簡(jiǎn)單的算法,主要分為兩個(gè)階段:1.標(biāo)記出需要回收的對(duì)象;2.在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對(duì)象。具體過(guò)程如下圖所示(圖片轉(zhuǎn)載自網(wǎng)絡(luò))。 
但它存在兩個(gè)不足: 1.效率問(wèn)題:標(biāo)記和清楚的兩個(gè)過(guò)程效率都不高,需要?dú)v遍兩次。 2.空間問(wèn)題:標(biāo)記清楚后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片,內(nèi)存碎片太多時(shí)會(huì)導(dǎo)致后面無(wú)法存放較大的對(duì)象。
該算法將內(nèi)存分為大小相等的兩塊,每次只是用其中的一塊,當(dāng)這一塊內(nèi)存用完了,就將還存活的對(duì)象復(fù)制到另一塊上面,并發(fā)已使用過(guò)的內(nèi)存一次性清理掉。這樣每次都是對(duì)半個(gè)內(nèi)存進(jìn)行回收,而且也不會(huì)產(chǎn)生內(nèi)存碎片的問(wèn)題。在實(shí)現(xiàn)層面上,只要一動(dòng)堆頂指針,按順序分配內(nèi)存即可,實(shí)現(xiàn)簡(jiǎn)單,運(yùn)行高效,但內(nèi)存只能利用一半。
這個(gè)算法利用不一樣的思路一次性解決了標(biāo)記-清除的全部問(wèn)題,但犧牲了很大的內(nèi)存空間,這個(gè)犧牲確實(shí)很大,但其勇敢的嘗試為GC算法的進(jìn)步賣(mài)出了重要的一步。 后面會(huì)有GC實(shí)現(xiàn)對(duì)應(yīng)這個(gè)算法,其實(shí)不一定需要犧牲一半的內(nèi)存,根據(jù)對(duì)象在內(nèi)存中的生存周期的分布特性可以減少犧牲的內(nèi)存,這也就是后面的新生代,老年代和survivor空間的由來(lái)。
由于復(fù)制算法在對(duì)象存活率較高時(shí)效率會(huì)很低,因此又有人提出了標(biāo)記-整理算法來(lái)解決這類(lèi)內(nèi)存的回收問(wèn)題。 這個(gè)算法跟最原始的標(biāo)記-清除的標(biāo)記階段一樣 ,但不一樣的是在清楚階段是,不是直接對(duì)可回收對(duì)象進(jìn)行清理,而是讓所有存活的對(duì)象都向一端移動(dòng),然后直接清理掉端邊界意外的內(nèi)存。 
當(dāng)前商業(yè)JVM的GC實(shí)現(xiàn)都是采用分代算法,即將內(nèi)存分成幾個(gè)區(qū)域,每個(gè)區(qū)域采用不同的回收算法處理,分區(qū)標(biāo)準(zhǔn)就是根據(jù)對(duì)象的存活周期長(zhǎng)短來(lái)區(qū)分。在生命周期短的區(qū)域使用復(fù)制算法,在生命周期長(zhǎng)的區(qū)域使用標(biāo)記-整理或標(biāo)記-清理算法。 了解了以上的GC算法,以后遇到有人希望你說(shuō)一下垃圾回收機(jī)制的時(shí)候,你可以反問(wèn)一句你希望聽(tīng)哪一種GC算法,保證一大半的已經(jīng)懵逼了。
為提高垃圾回收的效率,內(nèi)存按照對(duì)象的生命周期的長(zhǎng)短進(jìn)行分區(qū),存放生命周期長(zhǎng)的區(qū)域被稱(chēng)為老年代,存放生命周期短的區(qū)域被稱(chēng)為新生代。
Stop the world是指進(jìn)行GC時(shí)必須停頓所有Java執(zhí)行線程的現(xiàn)象。JVM中除了GC的所有工作都停止了,為什么會(huì)有這個(gè)現(xiàn)象呢,主要是為了實(shí)現(xiàn)準(zhǔn)確的GC,不會(huì)有人希望自己的數(shù)據(jù)會(huì)有一百萬(wàn)分之一的幾率被GC誤清除吧,需要進(jìn)行全局性的可達(dá)性分析即枚舉GC Roots。雖然有無(wú)數(shù)人月被投入到如何消除Stop the world現(xiàn)象但截至目前為止所有GC都需要一定時(shí)間的停頓完成GC Roots的枚舉,但在合理設(shè)置和開(kāi)發(fā)的前提下,這個(gè)時(shí)間可以被降低到一個(gè)可接受甚至是用戶(hù)無(wú)法察覺(jué)的程度。
上文已經(jīng)介紹過(guò)了,內(nèi)存中對(duì)象的生存時(shí)間并不一致,研究表明大部分對(duì)象都是“朝生夕死”的,所以并不需要1:1的分配老年代和新生代。 將內(nèi)存分為一塊較大的老年代Survivor和兩塊較小的新生代Eden,每次使用Eden和一塊Survivor。當(dāng)回收時(shí),將Eden和Survivor還存活的對(duì)象一次性的復(fù)制到另一塊Survivor中,會(huì)后清理掉Eden和用過(guò)的Survivor空間。默認(rèn)Eden和Survivor為8:1. 根據(jù)前面的統(tǒng)計(jì)情況,一般情況下空間是充足的,但特殊情況時(shí)會(huì)導(dǎo)致Survivor空間不足以保存現(xiàn)有數(shù)據(jù),這時(shí)就需要Eden進(jìn)行騰出一部分空間來(lái)保存對(duì)象了。這個(gè)策略 就是拿空間換時(shí)間,若空間不足再拿時(shí)間換空間,這個(gè)策略在一般情況下非常有效,但在不好的內(nèi)存配置情況或特殊業(yè)務(wù)場(chǎng)景下可能導(dǎo)致系統(tǒng)性能瓶頸的出現(xiàn)。 內(nèi)存分配策略:
對(duì)象優(yōu)先在新生代Eden分配大對(duì)象直接進(jìn)入老年代長(zhǎng)期存活的對(duì)象進(jìn)入老年代最早期的GC實(shí)現(xiàn),單線程的GC收集器,它在GC時(shí)會(huì)導(dǎo)致其他線程停止,從而造成Stop the world的發(fā)生。 JVM運(yùn)行在Client模式下的默認(rèn)新生代收集器,簡(jiǎn)單而高效,在回收空間有限時(shí),停頓時(shí)間完全可以接受。還是那句話沒(méi)有最好的,只有最合適的。
ParNew實(shí)際上就是Serial的多線程版本,Sever模式下的默認(rèn)新生代收集器,可以與后文的CMS收集器配合使用以發(fā)揮更強(qiáng)大的組合作用。作為多線程版本的ParNew在多CPU的表現(xiàn)要好于Serial。
新生代收集器,使用復(fù)制算法。這個(gè)搜集器的特色是能通過(guò)控制吞吐量(即能夠調(diào)整垃圾回收的所占用的時(shí)間)來(lái)調(diào)整停頓時(shí)間,即通過(guò)降低吞吐量來(lái)降低每次垃圾回收所停頓的時(shí)間。 由這個(gè)收集器可以看到一切都是針對(duì)應(yīng)用場(chǎng)景的,沒(méi)有最優(yōu)只有最合適。
是Serial的老年代版本,單線程,使用標(biāo)記-整理算法。主要是搭配Paralled Scavenge或作為CMS收集器的后備方案。
Parallel Old是Parallel Scavenge的老年代版本,在注重吞吐量以及CPU資源敏感的場(chǎng)合,都可以有優(yōu)先考慮Parallel Scavenge和Parallel Old的組合。
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓為目標(biāo)的收集器,比較適合重視服務(wù)響應(yīng)速度應(yīng)用場(chǎng)景?;臼墙o予標(biāo)記-算法,但具體過(guò)程可以分為:
初始標(biāo)記,標(biāo)記GC Roots直接關(guān)聯(lián)的對(duì)象,非???,但可能存在Stop the world并發(fā)標(biāo)記 ,耗時(shí)較長(zhǎng),但可以與用戶(hù)線程一起工作重新標(biāo)記,糾正可能由于并發(fā)標(biāo)記過(guò)程中可達(dá)性變化的對(duì)象,可能存在Stop the world并發(fā)清除,耗時(shí)較長(zhǎng),但可以與用戶(hù)線程一起工作這個(gè)收集器巧妙的設(shè)計(jì)使其可以盡可能的減少停頓時(shí)間,并將耗時(shí)的工作與用戶(hù)線程同時(shí)進(jìn)行。
但CMS也存在一定的問(wèn)題
對(duì)CPU資源非常敏感無(wú)法處理浮動(dòng)垃圾可能產(chǎn)生大量的碎片G1(Garbage First)收集器是目前最前沿的收集器,面向服務(wù)端應(yīng)用。 特點(diǎn)如下 并行與并發(fā):使用多CPU減少Stop the world時(shí)間 分代收集:不同的方式管理老年代和新生代 空間整合:標(biāo)記-整理的思路,無(wú)碎片化問(wèn)題 可預(yù)測(cè)的停頓:有模型可以預(yù)測(cè)停頓時(shí)間 它的過(guò)程與CMS類(lèi)似也是初始標(biāo)記,并發(fā)標(biāo)記,最終標(biāo)記,篩選回收。
1.《深入理解Java虛擬機(jī)》
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注