一.JVM主要包括四個(gè)部分:
1.類加載器(ClassLoader):在JVM啟動(dòng)時(shí)或者在類運(yùn)行時(shí)將需要的class加載到JVM中帆离。(右圖表示了從java源文件到JVM的整個(gè)過程,可配合理解
2.執(zhí)行引擎:負(fù)責(zé)執(zhí)行class文件中包含的字節(jié)碼指令(執(zhí)行引擎的工作機(jī)制结澄,這里也不細(xì)說了哥谷,這里主要介紹JVM結(jié)構(gòu));
3.內(nèi)存區(qū)(也叫運(yùn)行時(shí)數(shù)據(jù)區(qū)):是在JVM運(yùn)行的時(shí)候操作所分配的內(nèi)存區(qū)麻献。運(yùn)行時(shí)內(nèi)存區(qū)主要可以劃分為5個(gè)區(qū)域们妥,如圖:
- 方法區(qū)(Method Area):用于存儲類結(jié)構(gòu)信息的地方,包括常量池赎瑰、靜態(tài)變量王悍、構(gòu)造函數(shù)等。雖然JVM規(guī)范把方法區(qū)描述為堆的一個(gè)邏輯部分餐曼, 但它卻有個(gè)別名non-heap(非堆)压储,所以大家不要搞混淆了。方法區(qū)還包含一個(gè)運(yùn)行時(shí)常量池源譬。
- java堆(Heap):存儲java實(shí)例或者對象的地方集惋。這塊是GC的主要區(qū)域(后面解釋)。從存儲的內(nèi)容我們可以很容易知道踩娘,方法區(qū)和堆是被所有java線程共享的刮刑。
- java棧(Stack):java棧總是和線程關(guān)聯(lián)在一起养渴,每當(dāng)創(chuàng)建一個(gè)線程時(shí)雷绢,JVM就會為這個(gè)線程創(chuàng)建一個(gè)對應(yīng)的java棧。在這個(gè)java棧中又會包含多個(gè)棧幀理卑,每運(yùn)行一個(gè)方法就創(chuàng)建一個(gè)棧幀翘紊,用于存儲局部變量表、操作棧藐唠、方法返回值等帆疟。每一個(gè)方法從調(diào)用直至執(zhí)行完成的過程,就對應(yīng)一個(gè)棧幀在java棧中入棧到出棧的過程宇立。所以java棧是現(xiàn)成私有的踪宠。
- 程序計(jì)數(shù)器(PC Register):用于保存當(dāng)前線程執(zhí)行的內(nèi)存地址。由于JVM程序是多線程執(zhí)行的(線程輪流切換)妈嘹,所以為了保證線程切換回來后柳琢,還能恢復(fù)到原先狀態(tài),就需要一個(gè)獨(dú)立的計(jì)數(shù)器,記錄之前中斷的地方染厅,可見程序計(jì)數(shù)器也是線程私有的痘绎。
- 本地方法棧(Native Method Stack):和java棧的作用差不多,只不過是為JVM使用到的native方法服務(wù)的肖粮。
4.本地方法接口:主要是調(diào)用C或C++實(shí)現(xiàn)的本地方法及返回結(jié)果徽千。
二蜀漆、內(nèi)存分配
我覺得了解垃圾回收之前,得先了解JVM是怎么分配內(nèi)存的烫沙,然后識別哪些內(nèi)存是垃圾需要回收允坚,最后才是用什么方式回收魂那。
Java的內(nèi)存分配原理與C/C++不同,C/C++每次申請內(nèi)存時(shí)都要malloc進(jìn)行系統(tǒng)調(diào)用稠项,而系統(tǒng)調(diào)用發(fā)生在內(nèi)核空間涯雅,每次都要中斷進(jìn)行切換,這需要一定的開銷展运,而Java虛擬機(jī)是先一次性分配一塊較大的空間活逆,然后每次new時(shí)都在該空間上進(jìn)行分配和釋放,減少了系統(tǒng)調(diào)用的次數(shù)拗胜,節(jié)省了一定的開銷蔗候,這有點(diǎn)類似于內(nèi)存池的概念;二是有了這塊空間過后埂软,如何進(jìn)行分配和回收就跟GC機(jī)制有關(guān)了锈遥。
java一般內(nèi)存申請有兩種:靜態(tài)內(nèi)存和動(dòng)態(tài)內(nèi)存。很容易理解勘畔,編譯時(shí)就能夠確定的內(nèi)存就是靜態(tài)內(nèi)存所灸,即內(nèi)存是固定的,系統(tǒng)一次性分配炫七,比如int類型變量爬立;動(dòng)態(tài)內(nèi)存分配就是在程序執(zhí)行時(shí)才知道要分配的存儲空間大小,比如java對象的內(nèi)存空間诉字。根據(jù)上面我們知道懦尝,java棧、程序計(jì)數(shù)器壤圃、本地方法棧都是線程私有的陵霉,線程生就生,線程滅就滅伍绳,棧中的棧幀隨著方法的結(jié)束也會撤銷踊挠,內(nèi)存自然就跟著回收了。所以這幾個(gè)區(qū)域的內(nèi)存分配與回收是確定的,我們不需要管的效床。但是java堆和方法區(qū)則不一樣睹酌,我們只有在程序運(yùn)行期間才知道會創(chuàng)建哪些對象,所以這部分內(nèi)存的分配和回收都是動(dòng)態(tài)的剩檀。一般我們所說的垃圾回收也是針對的這一部分憋沿。
總之Stack的內(nèi)存管理是順序分配的,而且定長沪猴,不存在內(nèi)存回收問題辐啄;而Heap 則是為java對象的實(shí)例隨機(jī)分配內(nèi)存,不定長度运嗜,所以存在內(nèi)存分配和回收的問題壶辜;
三、GC機(jī)制
隨著程序的運(yùn)行担租,內(nèi)存中的實(shí)例對象砸民、變量等占據(jù)的內(nèi)存越來越多,如果不及時(shí)進(jìn)行回收奋救,會降低程序運(yùn)行效率岭参,甚至引發(fā)系統(tǒng)異常。
在上面介紹的五個(gè)內(nèi)存區(qū)域中菠镇,有3個(gè)是不需要進(jìn)行垃圾回收的:本地方法棧冗荸、程序計(jì)數(shù)器、虛擬機(jī)棧利耍。因?yàn)樗麄兊纳芷谑呛途€程同步的蚌本,隨著線程的銷毀,他們占用的內(nèi)存會自動(dòng)釋放隘梨。所以程癌,只有方法區(qū)和堆區(qū)需要進(jìn)行垃圾回收,回收的對象就是那些不存在任何引用的對象轴猎。
3.1 查找算法
經(jīng)典的引用計(jì)數(shù)算法嵌莉,每個(gè)對象添加到引用計(jì)數(shù)器,每被引用一次捻脖,計(jì)數(shù)器+1锐峭,失去引用,計(jì)數(shù)器-1可婶,當(dāng)計(jì)數(shù)器在一段時(shí)間內(nèi)為0時(shí)沿癞,即認(rèn)為該對象可以被回收了。但是這個(gè)算法有個(gè)明顯的缺陷:當(dāng)兩個(gè)對象相互引用矛渴,但是二者都已經(jīng)沒有作用時(shí)椎扬,理應(yīng)把它們都回收,但是由于它們相互引用,不符合垃圾回收的條件蚕涤,所以就導(dǎo)致無法處理掉這一塊內(nèi)存區(qū)域筐赔。因此,Sun的JVM并沒有采用這種算法揖铜,而是采用一個(gè)叫——根搜索算法茴丰,
基本思想是:從一個(gè)叫GC Roots的根節(jié)點(diǎn)出發(fā),向下搜索蛮位,如果一個(gè)對象不能達(dá)到GC Roots的時(shí)候较沪,說明該對象不再被引用,可以被回收失仁。如上圖中的Object5、Object6们何、Object7萄焦,雖然它們?nèi)齻€(gè)依然相互引用,但是它們其實(shí)已經(jīng)沒有作用了冤竹,這樣就解決了引用計(jì)數(shù)算法的缺陷拂封。
補(bǔ)充概念,在JDK1.2之后引入了四個(gè)概念:強(qiáng)引用鹦蠕、軟引用冒签、弱引用、虛引用钟病。
強(qiáng)引用:new出來的對象都是強(qiáng)引用萧恕,GC無論如何都不會回收,即使拋出OOM異常肠阱。
軟引用:只有當(dāng)JVM內(nèi)存不足時(shí)才會被回收票唆。
弱引用:只要GC,就會立馬回收,不管內(nèi)存是否充足屹徘。
虛引用:可以忽略不計(jì)走趋,JVM完全不會在乎虛引用,你可以理解為它是來湊數(shù)的噪伊,湊夠”四大天王”簿煌。它唯一的作用就是做一些跟蹤記錄,輔助finalize函數(shù)的使用鉴吹。
最后總結(jié)姨伟,什么樣的類需要被回收:
a.該類的所有實(shí)例都已經(jīng)被回收;
b.加載該類的ClassLoad已經(jīng)被回收拙寡;
c.該類對應(yīng)的反射類java.lang.Class對象沒有被任何地方引用授滓。
3.2 內(nèi)存分區(qū)
內(nèi)存主要被分為三塊:新生代(Youn Generation)、舊生代(Old Generation)、持久代(Permanent Generation)般堆。三代的特點(diǎn)不同在孝,造就了他們使用的GC算法不同淮摔,新生代適合生命周期較短私沮,快速創(chuàng)建和銷毀的對象,舊生代適合生命周期較長的對象和橙,持久代在Sun Hotpot虛擬機(jī)中就是指方法區(qū)(有些JVM根本就沒有持久代這一說法)仔燕。
新生代(Youn Generation):大致分為Eden區(qū)和Survivor區(qū),Survivor區(qū)又分為大小相同的兩部分:FromSpace和ToSpace魔招。新建的對象都是從新生代分配內(nèi)存晰搀,Eden區(qū)不足的時(shí)候,會把存活的對象轉(zhuǎn)移到Survivor區(qū)办斑。當(dāng)新生代進(jìn)行垃圾回收時(shí)會出發(fā)Minor GC(也稱作Youn GC)外恕。
舊生代(Old Generation):舊生代用于存放新生代多次回收依然存活的對象,如緩存對象乡翅。當(dāng)舊生代滿了的時(shí)候就需要對舊生代進(jìn)行回收鳞疲,舊生代的垃圾回收稱作Major GC(也稱作Full GC)。
持久代(Permanent Generation):在Sun 的JVM中就是方法區(qū)的意思蠕蚜,盡管大多數(shù)JVM沒有這一代尚洽。
3.3 GC算法
常見的GC算法:復(fù)制、標(biāo)記-清除和標(biāo)記-壓縮
復(fù)制:復(fù)制算法采用的方式為從根集合進(jìn)行掃描靶累,將存活的對象移動(dòng)到一塊空閑的區(qū)域腺毫,當(dāng)存活的對象較少時(shí),復(fù)制算法會比較高效(新生代的Eden區(qū)就是采用這種算法)尺铣,其帶來的成本是需要一塊額外的空閑空間和對象的移動(dòng)拴曲。
標(biāo)記-清除:該算法采用的方式是從跟集合開始掃描,對存活的對象進(jìn)行標(biāo)記凛忿,標(biāo)記完畢后澈灼,再掃描整個(gè)空間中未被標(biāo)記的對象,并進(jìn)行清除店溢。
清除階段清理的是沒有被引用的對象叁熔,存活的對象被保留。
標(biāo)記-清除動(dòng)作不需要移動(dòng)對象床牧,且僅對不存活的對象進(jìn)行清理荣回,在空間中存活對象較多的時(shí)候,效率較高戈咳,但由于只是清除心软,沒有重新整理壕吹,因此會造成內(nèi)存碎片。
標(biāo)記-壓縮:該算法與標(biāo)記-清除算法類似删铃,都是先對存活的對象進(jìn)行標(biāo)記耳贬,但是在清除后會把活的對象向左端空閑空間移動(dòng),然后再更新其引用對象的指針
四猎唁、垃圾收集器
在JVM中咒劲,GC是由垃圾回收器來執(zhí)行,所以诫隅,在實(shí)際應(yīng)用場景中腐魂,我們需要選擇合適的垃圾收集器,下面我們介紹一下垃圾收集器逐纬。
4.1 串行收集器(Serial GC)
Serial GC是最古老也是最基本的收集器蛔屹,但是現(xiàn)在依然廣泛使用,JAVA SE5和JAVA SE6中客戶端虛擬機(jī)采用的默認(rèn)配置豁生。比較適合于只有一個(gè)處理器的系統(tǒng)判导。在串行處理器中minor和major GC過程都是用一個(gè)線程進(jìn)行回收的。它的最大特點(diǎn)是在進(jìn)行垃圾回收時(shí)沛硅,需要對所有正在執(zhí)行的線程暫停(stop the world),對于有些應(yīng)用是難以接受的绕辖,但是如果應(yīng)用的實(shí)時(shí)性要求不是那么高摇肌,只要停頓的時(shí)間控制在N毫秒之內(nèi),大多數(shù)應(yīng)用還是可以接受的仪际,而且事實(shí)上围小,它并沒有讓我們失望,幾十毫秒的停頓树碱,對于我們客戶機(jī)是完全可以接受的肯适,該收集器適用于單CPU、新生代空間較小且對暫停時(shí)間要求不是特別高的應(yīng)用上成榜,是client級別的默認(rèn)GC方式框舔。
4.2 ParNew GC
基本和Serial GC一樣,但本質(zhì)區(qū)別是加入了多線程機(jī)制赎婚,提高了效率刘绣,這樣它就可以被用于服務(wù)端上(server),同時(shí)它可以與CMS GC配合挣输,所以纬凤,更加有理由將他用于server端。
4.3 Parallel Scavenge GC
在整個(gè)掃描和復(fù)制過程采用多線程的方式進(jìn)行撩嚼,適用于多CPU停士、對暫停時(shí)間要求較短的應(yīng)用挖帘,是server級別的默認(rèn)GC方式。
4.4 CMS (Concurrent Mark Sweep)收集器
該收集器的目標(biāo)是解決Serial GC停頓的問題恋技,以達(dá)到最短回收時(shí)間拇舀。常見的B/S架構(gòu)的應(yīng)用就適合這種收集器,因?yàn)槠涓卟l(fā)猖任、高響應(yīng)的特點(diǎn)你稚,CMS是基于標(biāo)記-清楚算法實(shí)現(xiàn)的。
CMS收集器的優(yōu)點(diǎn):并發(fā)收集朱躺、低停頓刁赖,但遠(yuǎn)沒有達(dá)到完美;
CMS收集器的缺點(diǎn):
a.CMS收集器對CPU資源非常敏感长搀,在并發(fā)階段雖然不會導(dǎo)致用戶停頓宇弛,但是會占用CPU資源而
導(dǎo)致應(yīng)用程序變慢,總吞吐量下降源请。
b.CMS收集器無法處理浮動(dòng)垃圾枪芒,可能出現(xiàn)“Concurrnet Mode Failure”,失敗而導(dǎo)致另一次的Full GC谁尸。
c.CMS收集器是基于標(biāo)記-清除算法的實(shí)現(xiàn)舅踪,因此也會產(chǎn)生碎片。
4.5 G1收集器
相比CMS收集器有不少改進(jìn)良蛮,首先抽碌,基于標(biāo)記-壓縮算法,不會產(chǎn)生內(nèi)存碎片决瞳,其次可以比較精確的控制停頓货徙。
4.6 Serial Old收集器
Serial Old是Serial收集器的老年代版本,它同樣使用一個(gè)單線程執(zhí)行收集皮胡,使用“標(biāo)記-整理”算法痴颊。主要使用在Client模式下的虛擬機(jī)。
4.7 Parallel Old收集器
Parallel Old是Parallel Scavenge收集器的老年代版本屡贺,使用多線程和“標(biāo)記-整理”算法蠢棱。
4.8 RTSJ垃圾收集器
RTSJ垃圾收集器,用于Java實(shí)時(shí)編程