今天對(duì)JVM進(jìn)行了簡(jiǎn)單的學(xué)習(xí)熄诡,做了一個(gè)總結(jié)吮铭,如果有不對(duì)的地方還請(qǐng)大家指正墙懂。
一.java代碼如何運(yùn)行
編譯型語(yǔ)言:程序在執(zhí)行之前需要一個(gè)專(zhuān)門(mén)的編譯過(guò)程兼犯,把程序編譯成 為機(jī)器語(yǔ)言的文件磅崭,運(yùn)行時(shí)不需要重新翻譯垫竞,直接使用編譯的結(jié)果就行了缓屠。程序執(zhí)行效率高,依賴(lài)編譯器期丰,跨平臺(tái)性差些群叶。如C、C++钝荡、Delphi等街立。
解釋型語(yǔ)言:程序不需要編譯,程序在運(yùn)行時(shí)才翻譯成機(jī)器語(yǔ)言化撕,每執(zhí) 行一次都要翻譯一次几晤。因此效率比較低约炎。比如Basic語(yǔ)言植阴,專(zhuān)門(mén)有一個(gè)解釋器能夠直接執(zhí)行Basic程 序蟹瘾,每個(gè)語(yǔ)句都是執(zhí)行的時(shí)候才翻譯。(在運(yùn)行程序的時(shí)候才翻譯掠手,專(zhuān)門(mén)有一個(gè)解釋器去進(jìn)行翻譯憾朴,每個(gè)語(yǔ)句都是執(zhí)行的時(shí)候才翻譯。效率比較低喷鸽,依賴(lài)解釋器众雷,跨 平臺(tái)性好.)
Java程序從源文件創(chuàng)建到程序運(yùn)行要經(jīng)過(guò)兩大步驟:1、源文件由編譯器編譯成字節(jié)碼(ByteCode) ?2做祝、字節(jié)碼由java虛擬機(jī)解釋成機(jī)器語(yǔ)言運(yùn)行砾省。因?yàn)閖ava程序既要編譯同時(shí)也要經(jīng)過(guò)JVM的解釋運(yùn)行,所以說(shuō)Java被稱(chēng)為半解釋語(yǔ)言(?"semi-interpreted" language)混槐。
① 編譯過(guò)程:創(chuàng)建完源文件之后编兄,程序會(huì)先被編譯為.class文件。Java編譯一個(gè)類(lèi)時(shí)声登,如果這個(gè)類(lèi)所依賴(lài)的類(lèi)還沒(méi)有被編譯狠鸳,編譯器就會(huì)先編譯這個(gè)被依賴(lài)的類(lèi),然后引用悯嗓,否則直接引用件舵,這個(gè)有點(diǎn)象make。如果java編譯器在指定目錄下找不到該類(lèi)所其依賴(lài)的類(lèi)的.class文件或者.java源文件的話脯厨,編譯器話報(bào)“cant find symbol”的錯(cuò)誤铅祸。
編譯后的字節(jié)碼文件格式主要分為兩部分:常量池和方法字節(jié)碼。常量池記錄的是代碼出現(xiàn)過(guò)的所有token(類(lèi)名合武,成員變量名等等)以及符號(hào)引用(方法引用个少,成員變量引用等等)
②運(yùn)行:java類(lèi)運(yùn)行的過(guò)程大概可分為兩個(gè)過(guò)程:1、類(lèi)的加載 ?2眯杏、類(lèi)的執(zhí)行夜焦。需要說(shuō)明的是:JVM主要在程序第一次主動(dòng)使用類(lèi)的時(shí)候,才會(huì)去加載該類(lèi)岂贩。也就是說(shuō)茫经,JVM并不是在一開(kāi)始就把一個(gè)程序就所有的類(lèi)都加載到內(nèi)存中,而是到不得不用的時(shí)候才把它加載進(jìn)來(lái)萎津,而且只加載一次卸伞。
下面通過(guò)以下這個(gè)java程序,來(lái)說(shuō)明java程序從編譯到最后運(yùn)行的整個(gè)流程锉屈。代碼如下:
//MainApp.java??
publicclass?MainApp?{??
publicstaticvoid?main(String[]?args)?{??
Animal?animal?=new?Animal("Puppy");??
????????animal.printName();??
????}??
}??
//Animal.java??
publicclass?Animal?{??
public?String?name;??
public?Animal(String?name)?{??
this.name?=?name;??
????}??
publicvoid?printName()?{??
System.out.println("Animal?["+name+"]");??
????}??
}
1.在編譯好java程序得到MainApp.class文件后荤傲,在命令行上敲java AppMain。系統(tǒng)就會(huì)啟動(dòng)一個(gè)jvm進(jìn)程颈渊,jvm進(jìn)程從classpath路徑中找到一個(gè)名為AppMain.class的二進(jìn)制文件遂黍,將MainApp的類(lèi)信息加載到運(yùn)行時(shí)數(shù)據(jù)區(qū)的方法區(qū)內(nèi)终佛,這個(gè)過(guò)程叫做MainApp類(lèi)的加載。
2.然后JVM找到AppMain的主函數(shù)入口雾家,開(kāi)始執(zhí)行main函數(shù)铃彰。
3.main函數(shù)的第一條命令是Animal ?animal = new Animal("Puppy");就是讓JVM創(chuàng)建一個(gè)Animal對(duì)象,但是這時(shí)候方法區(qū)中沒(méi)有Animal類(lèi)的信息芯咧,所以JVM馬上加載Animal類(lèi)牙捉,把Animal類(lèi)的類(lèi)型信息放到方法區(qū)中。
4.加載完Animal類(lèi)之后敬飒,Java虛擬機(jī)做的第一件事情就是在堆區(qū)中為一個(gè)新的Animal實(shí)例分配內(nèi)存, 然后調(diào)用構(gòu)造函數(shù)初始化Animal實(shí)例邪铲,這個(gè)Animal實(shí)例持有著指向方法區(qū)的Animal類(lèi)的類(lèi)型信息(其中包含有方法表,java動(dòng)態(tài)綁定的底層實(shí)現(xiàn))的引用无拗。
5.當(dāng)使用animal.printName()的時(shí)候霜浴,JVM根據(jù)animal引用找到Animal對(duì)象,然后根據(jù)Animal對(duì)象持有的引用定位到方法區(qū)中Animal類(lèi)的類(lèi)型信息的方法表蓝纲,獲得printName()函數(shù)的字節(jié)碼的地址阴孟。
6.開(kāi)始運(yùn)行printName()函數(shù)。
二.JVM執(zhí)行過(guò)程
JVM啟動(dòng)后税迷,對(duì)操作系統(tǒng)來(lái)說(shuō)永丝,JVM是一個(gè)的進(jìn)程,這個(gè)進(jìn)程的基本結(jié)構(gòu)如上圖所示箭养。它包括:類(lèi)加載器子系統(tǒng)慕嚷、運(yùn)行時(shí)數(shù)據(jù)區(qū)、執(zhí)行引擎和本地方法接口毕泌。
運(yùn)行時(shí)數(shù)據(jù)區(qū)是JVM從操作系統(tǒng)申請(qǐng)來(lái)的堆空間和操作系統(tǒng)給JVM分配的椇燃欤空間的總稱(chēng)。JVM為了運(yùn)行Java程序撼泛,又進(jìn)一步對(duì)運(yùn)行時(shí)數(shù)據(jù)區(qū)進(jìn)行了劃分挠说,劃分為Java方法區(qū)、Java堆愿题、Java棧损俭、PC寄存器、本地方法棧等潘酗,這里JVM從操作系統(tǒng)申請(qǐng)來(lái)的堆空間被劃分為方法區(qū)和Java堆杆兵,操作系統(tǒng)給JVM分配的棧空間構(gòu)成Java棧仔夺。
三.JVM總體結(jié)構(gòu)
1)類(lèi)加載子系統(tǒng)與方法區(qū):類(lèi)加載子系統(tǒng)負(fù)責(zé)從文件系統(tǒng)或者網(wǎng)絡(luò)中加載Classs信息琐脏,加載的類(lèi)信息存放于一塊稱(chēng)為方法區(qū)的內(nèi)存空間。除了類(lèi)的信息外,方法區(qū)中可能還會(huì)存放運(yùn)行時(shí)常量池信息,包括字符串字面量和數(shù)字常量(這部分常量信息是Classs文件中常量池部分的內(nèi)存映射)日裙。classloader就是類(lèi)加載子系統(tǒng)的一個(gè)組件吹艇,但是類(lèi)加載子系統(tǒng)不僅僅有classloader,還有文件校驗(yàn)等功能阅签。這一部分負(fù)責(zé)的內(nèi)容總體來(lái)說(shuō)掐暮,就是負(fù)責(zé)把我們的java文件加載到虛擬機(jī)里面蝎抽。
2) java 堆: java 堆在虛擬機(jī)啟動(dòng)的時(shí)候建立政钟,它是java程序最主要的內(nèi)存工作區(qū)域。幾乎所有的java對(duì)象實(shí)例都存放在java 堆中樟结。堆空間是所有線程共享的养交,這是-塊與java應(yīng)用密切相關(guān)的內(nèi)存空間。這里是重點(diǎn)瓢宦,稍后會(huì)詳細(xì)就說(shuō)碎连。
3)直接內(nèi)存: java 的NIO庫(kù)允許java程序使用直接內(nèi)存。直接內(nèi)存是在java堆外的驮履、直接向系統(tǒng)申請(qǐng)的內(nèi)存空間鱼辙。通常訪問(wèn)直接內(nèi)存的速度會(huì)優(yōu)于java堆。因此出于性能的考慮玫镐,讀寫(xiě)頻繁的場(chǎng)合可能會(huì)考慮使用直接內(nèi)存倒戏。由于直接內(nèi)存在java堆外,因此它的大小不會(huì)直接受限于Xmx指定的最大堆大小恐似,但是系統(tǒng)內(nèi)存是有限的杜跷,java 堆和直接內(nèi)存的總和依然受限于操作系統(tǒng)能給出的最大內(nèi)存。直接映射到物理內(nèi)存矫夷,大小取決于物理內(nèi)存葛闷,不受JVM分配給堆的大小限制。
4)垃圾回收系統(tǒng):垃圾回收系統(tǒng)是java虛擬機(jī)的重要組成部分,垃圾回收器可以對(duì)方法區(qū)双藕、java堆和直接內(nèi)存進(jìn)行回收淑趾。其中,java 堆是垃圾收集器的工作重點(diǎn)忧陪。和C/C++不同治笨,java中所有的對(duì)象空間釋放都是隱式的,也就是說(shuō)赤嚼,java 中沒(méi)有類(lèi)似free(咸者delete()這樣的函數(shù)釋放指定的內(nèi)存區(qū)域旷赖。對(duì)于不再使用的垃圾對(duì)象,垃圾回收系統(tǒng)會(huì)在后臺(tái)黑默工作,默黑默查找更卒、標(biāo)識(shí)并釋放垃圾對(duì)象,完成包括java堆等孵、方法區(qū)和直接內(nèi)存中的全自動(dòng)化管理。
5) java棧:每一個(gè)java虛擬機(jī)線程都有一一個(gè)私有的java棧蹂空,一個(gè)線程的java棧在線程創(chuàng)建的時(shí)候被創(chuàng)建俯萌,java 棧中保存著幀信息果录,java 棧中保存著局部變量(也稱(chēng)為棧變量)、方法參數(shù)咐熙,同時(shí)和java方法的調(diào)用弱恒、返回密切相關(guān)。
6)本地方法棧:本地方法棧和java棧非常類(lèi)似棋恼,最大的不同在于java 棧用于方法的調(diào)用返弹,而本地方法棧則用于本地方法(nativemothod,也就是操作系統(tǒng)的api爪飘,所以為了)的調(diào)用义起,作為對(duì)java虛擬機(jī)的重要擴(kuò)展, java 虛擬機(jī)允許java直接調(diào)用本地方法(通常使用C編寫(xiě))
7) PC ( Program Counter): PC寄存器也是每一一個(gè)線程私有的空間,java 虛擬機(jī)會(huì)為每一-個(gè)java線程創(chuàng)建PC寄存器师崎。在任意時(shí)刻默终,一個(gè)java線程總是在執(zhí)行一個(gè)方法, 這個(gè)正在被執(zhí)行的方法稱(chēng)為當(dāng)前方法犁罩。如果當(dāng)前方法不是本地方法齐蔽,PC 寄存器就會(huì)指向當(dāng)前正在被執(zhí)行的指令。如果當(dāng)前方法是本地方法床估,那么PC寄存器的值就是undefined
8)執(zhí)行引擎:執(zhí)行引擎是java虛擬機(jī)的最核心組件之- - ,它負(fù)責(zé)執(zhí)行虛擬機(jī)的字節(jié)碼含滴,現(xiàn)代虛擬機(jī)為了提高執(zhí)行效率,會(huì)使用即時(shí)編譯技術(shù)將方法編譯成機(jī)器碼后再執(zhí)行顷窒。
Java HotSpot Client VM(-lient),為在客戶(hù)端環(huán)境中減少啟動(dòng)時(shí)間而優(yōu)化;
Java Hotspot server VM(-server),為在服務(wù)器環(huán)境中最大化程序執(zhí)行速度而設(shè)計(jì)蛙吏。比較占內(nèi)存
Java HotSpot Client模式和Server模式的區(qū)別
當(dāng)虛擬機(jī)運(yùn)行在-client模式的時(shí)候,使用的是一-個(gè)代號(hào)為C1的輕量級(jí)編譯器鞋吉,而-server模式啟動(dòng)的虛擬機(jī)采用相對(duì)重量級(jí)代號(hào)為C2的編譯器. C2比C1編譯器編譯的相對(duì)徹底服務(wù)起來(lái)之后鸦做,性能更高
? ? ? 重點(diǎn):在部分JDK1.6版本和后續(xù)的JDK版本(64位系統(tǒng))中,-client 參數(shù)已經(jīng)不起作用了谓着,Server 模式成為唯一泼诱,但是32位還可以。具體如何切換可以參考這篇文章
https://blog.csdn.net/jacksonary/article/details/80334040
四.Java堆結(jié)構(gòu)與內(nèi)存分代
①JVM的內(nèi)存分代策略
? ? ? ?? Java虛擬機(jī)根據(jù)對(duì)象存活的周期不同赊锚,把堆內(nèi)存劃分為幾塊治筒,一般分為新生代、老年代和永久代(對(duì)HotSpot虛擬機(jī)而言) ,這就是JVM的內(nèi)存分代策略舷蒲。
?②為什么要分代?
? ? ? 堆內(nèi)存是虛擬機(jī)管理的內(nèi)存中最大的-塊,也是垃圾回收最頻繁的一塊區(qū)域耸袜,我們程序所有的對(duì)象實(shí)例都存放在堆內(nèi)存中。給堆內(nèi)存分代是為了提高對(duì)象內(nèi)存分配和垃圾回收的效率牲平。試想-下堤框,如果堆內(nèi)存沒(méi)有區(qū)域劃分,所有的新創(chuàng)建的對(duì)象和生命周期很長(zhǎng)的對(duì)象放在一起,隨著程序的執(zhí)行,堆內(nèi)存需要頻繁進(jìn)行垃圾收集,而每次回收都要遍歷所有的對(duì)象,遍歷這些對(duì)象所花費(fèi)的時(shí)間代價(jià)是巨大的,會(huì)嚴(yán)重影響我們的GC效率蜈抓。
? ? ? 有了內(nèi)存分代启绰,情況就不同了,新創(chuàng)建的對(duì)象會(huì)在新生代中分配內(nèi)存,經(jīng)過(guò)多次回收仍然存活下來(lái)的對(duì)象存放在老年代中沟使,靜態(tài)屬性委可、類(lèi)信息等存放在永久代中,新生代中的對(duì)象存活時(shí)間短腊嗡,只需要在新生代區(qū)域中頻繁進(jìn)行GC,老年代中對(duì)象生命周期長(zhǎng)着倾,內(nèi)存回收的頻率相對(duì)較低,不需要頻繁進(jìn)行回收,永久代中回收效果太差, 一般不進(jìn)行垃圾回收叽唱,還可以根據(jù)不同年代的特點(diǎn)屈呕,采用不同的垃圾收集算法微宝。分代垃圾收集大大提升了垃圾收集效率棺亭,這些都是JVM分代的好處。
③內(nèi)存分代劃分
Java虛擬機(jī)將堆內(nèi)存劃分為新生代蟋软、老年戰(zhàn)和永久代镶摘,永久代是HotSpaot 虛擬機(jī)特有的概念,它采用永久代的方式來(lái)實(shí)現(xiàn)方法區(qū),其他的虛擬機(jī)實(shí)現(xiàn)沒(méi)有這一概念,而且HotSpot也有取消永久代的趨勢(shì)岳守,在JDK 1.7中HotSpot已經(jīng)開(kāi)始了“去永久化”凄敢,把原本放在永久代的字符串常量池移出。永久代主要存放常量湿痢、類(lèi)信息涝缝、靜態(tài)變量等數(shù)據(jù)(移植到方法區(qū)),與垃圾回收關(guān)系不大譬重,新生代和老年代是垃圾回收的主要區(qū)域拒逮。內(nèi)存分代示意圖如下:
新生代(Young)
?新生成的對(duì)象優(yōu)先存放在新生代中,新生代對(duì)象朝生夕死,存活率很低,在新生代中臀规,常規(guī)應(yīng)用進(jìn)行一次垃圾收集-般可以回收70% ~ 95%的空間滩援,回收效率很高。
老年代(Old?Generationn)
? ? ? ? 在新生代中經(jīng)歷了多次(具體看虛擬機(jī)配置的閥值)?GC后仍然存活下來(lái)的對(duì)象會(huì)進(jìn)入老年代中塔嬉。老年代中的對(duì)象生命周期較長(zhǎng)玩徊,存活率比較高,在老年代中進(jìn)行GC的頻率相對(duì)而言較低,而且回收的速度也比較慢谨究。
永久代(Permanent?Generationn)
? ? ? ? 永久代存儲(chǔ)類(lèi)信息恩袱、常量、靜態(tài)變量胶哲、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)畔塔,對(duì)這一區(qū)域而言,Java虛擬機(jī)規(guī)范指出可以不進(jìn)行垃圾收集,一般而言不會(huì)進(jìn)行垃圾回收俩檬。
五.哪些垃圾需要被回收
? ? ? ? 大家都知道JVM的內(nèi)存結(jié)構(gòu)包括五大區(qū)域:程序計(jì)數(shù)器萎胰、虛擬機(jī)棧、本地方法棧棚辽、堆區(qū)技竟、方法區(qū)。其中程序計(jì)數(shù)器屈藐、虛擬機(jī)棧榔组、本地方法棧3個(gè)區(qū)域隨線程而生、隨線程而滅联逻,因此這幾個(gè)區(qū)域的內(nèi)存分配和回收都具備確定性搓扯,就不需要過(guò)多考慮回收的問(wèn)題,因?yàn)榉椒ńY(jié)束或者線程結(jié)束時(shí)包归,內(nèi)存自然就跟隨著回收了锨推。而Java堆區(qū)和方法區(qū)則不一樣,這部分內(nèi)存的分配和回收是動(dòng)態(tài)的公壤,正是垃圾收集器所需關(guān)注的部分换可。
?垃圾收集器在對(duì)堆區(qū)和方法區(qū)進(jìn)行回收前,首先要確定這些區(qū)域的對(duì)象哪些可以被回收厦幅,哪些暫時(shí)還不能回收沾鳄,這就要用到判斷對(duì)象是否存活的算法。
①引用計(jì)數(shù)( Reference Counting ):
? ? ? ? 比較古老的回收算法确憨。原理是此對(duì)象有一個(gè)引用,即增加一個(gè)計(jì)數(shù),刪除一個(gè)引用則減少一個(gè)計(jì)數(shù)译荞。垃圾回收時(shí),只用收集計(jì)數(shù)為0的對(duì)象。此算法最致命的是無(wú)法處理循環(huán)引用的問(wèn)題休弃⊥碳撸看下面的例子。
public classReferenceFindTest{
? ? publicstaticvoidmain(String[] args){
? ? ? ? MyObject object1 = new MyObject();
? ? ? ? MyObject object2 = new MyObject();
? ? ? ? object1.object = object2;
? ? ? ? object2.object = object1;
? ? ? ? object1 = null;
? ? ? ? object2 = null;
? ? }
}
這段代碼是用來(lái)驗(yàn)證引用計(jì)數(shù)算法不能檢測(cè)出循環(huán)引用玫芦。最后面兩句將object1和object2賦值為null浆熔,也就是說(shuō)object1和object2指向的對(duì)象已經(jīng)不可能再被訪問(wèn),但是由于它們互相引用對(duì)方桥帆,導(dǎo)致它們的引用計(jì)數(shù)器都不為0医增,那么垃圾收集器就永遠(yuǎn)不會(huì)回收它們。
②可達(dá)性分析算法
? ? ? ? 可達(dá)性分析算法是從離散數(shù)學(xué)中的圖論引入的老虫,程序把所有的引用關(guān)系看作一張圖叶骨,從一個(gè)節(jié)點(diǎn)GC ROOT開(kāi)始,尋找對(duì)應(yīng)的引用節(jié)點(diǎn)祈匙,找到這個(gè)節(jié)點(diǎn)以后忽刽,繼續(xù)尋找這個(gè)節(jié)點(diǎn)的引用節(jié)點(diǎn)天揖,當(dāng)所有的引用節(jié)點(diǎn)尋找完畢之后,剩余的節(jié)點(diǎn)則被認(rèn)為是沒(méi)有被引用到的節(jié)點(diǎn)跪帝,即無(wú)用的節(jié)點(diǎn)今膊,無(wú)用的節(jié)點(diǎn)將會(huì)被判定為是可回收的對(duì)象。
在Java語(yǔ)言中伞剑,可作為GC Roots的對(duì)象包括下面幾種:
??a) 虛擬機(jī)棧中引用的對(duì)象(棧幀中的本地變量表)斑唬;
??b) 方法區(qū)中類(lèi)靜態(tài)屬性引用的對(duì)象;
??c) 方法區(qū)中常量引用的對(duì)象黎泣;
??d) 本地方法棧中JNI(Native方法)引用的對(duì)象恕刘。
③方法區(qū)如何判斷是否需要回收
? ? ? ? 方法區(qū)主要回收的內(nèi)容有:廢棄常量和無(wú)用的類(lèi)。對(duì)于廢棄常量也可通過(guò)引用的可達(dá)性來(lái)判斷抒倚,但是對(duì)于無(wú)用的類(lèi)則需要同時(shí)滿(mǎn)足下面3個(gè)條件:
該類(lèi)所有的實(shí)例都已經(jīng)被回收褐着,也就是Java堆中不存在該類(lèi)的任何實(shí)例;
加載該類(lèi)的ClassLoader已經(jīng)被回收托呕;
該類(lèi)對(duì)應(yīng)的java.lang.Class對(duì)象沒(méi)有在任何地方被引用含蓉,無(wú)法在任何地方通過(guò)反射訪問(wèn)該類(lèi)的方法。
關(guān)于類(lèi)加載的原理镣陕,也是阿里面試的主角谴餐,面試官也問(wèn)過(guò)我比如:能否自己定義String姻政,答案是不行呆抑,因?yàn)閖vm在加載類(lèi)的時(shí)候會(huì)執(zhí)行雙親委派,
原理請(qǐng)參考:http://www.cnblogs.com/aspirant/p/7200523.html
五.常用的垃圾收集算法
①?gòu)?fù)制算法( Copying ):
? ? ? ? 此算法把內(nèi)存空間劃為兩個(gè)相等的區(qū)域,每次只使用其中一個(gè)區(qū)域汁展。垃圾回收時(shí),遍歷當(dāng)前使用區(qū)域,把正在使用中的對(duì)象復(fù)制到另外一個(gè)區(qū)域中鹊碍。次算法每次只處理正在使用中的對(duì)象,因此復(fù)制成本比較小,同時(shí)復(fù)制過(guò)去以后還能進(jìn)行相應(yīng)的內(nèi)存整理,不會(huì)出現(xiàn)“碎片”問(wèn)題食绿。當(dāng)然,此算法的缺點(diǎn)也是很明顯的,就是需要兩倍內(nèi)存空間侈咕。這個(gè)就是用在堆分代回收那里的算法。
②標(biāo)記-清除法(Mark-Sweep)
? ? ? ? 是JVM垃圾回收算法中最古老的一個(gè)器紧,該算法共分成兩個(gè)階段耀销,第一階段從引用根節(jié)點(diǎn)開(kāi)始標(biāo)記所有被引用的對(duì)象,第二階段遍歷整個(gè)堆铲汪,清除未被標(biāo)記的對(duì)象熊尉。該算法的缺點(diǎn)是需要暫停整個(gè)應(yīng)用,并且會(huì)產(chǎn)生內(nèi)存碎片掌腰。
我們可以看到在回收以后未使用的空間是不連續(xù)的狰住,不連續(xù)的空間也就是內(nèi)存碎片,這會(huì)影響到存儲(chǔ)齿梁。
③標(biāo)記-整理法(Mark-Compact)
此算法結(jié)合了標(biāo)記-清楚算法和復(fù)制算法的優(yōu)點(diǎn)催植,也分為兩個(gè)階段肮蛹,第一階段從引用根節(jié)點(diǎn)開(kāi)始標(biāo)記所有被引用的對(duì)象,第二階段遍歷整個(gè)堆创南,在回收不存活的對(duì)象占用的空間后伦忠,會(huì)將所有的存活對(duì)象往左端空閑空間移動(dòng),并更新對(duì)應(yīng)的指針稿辙。標(biāo)記-整理算法是在標(biāo)記-清除算法的基礎(chǔ)上缓苛,又進(jìn)行了對(duì)象的移動(dòng),因此成本更高邓深,但是卻解決了內(nèi)存碎片的問(wèn)題未桥,按順序排放,同時(shí)解決了復(fù)制算法所需內(nèi)存空間過(guò)大的問(wèn)題芥备。
④分代收集算法
分代收集算法是目前大部分JVM的垃圾收集器采用的算法冬耿。它的核心思想是根據(jù)對(duì)象存活的生命周期將內(nèi)存劃分為若干個(gè)不同的區(qū)域。一般情況下將堆區(qū)劃分為老年代(Tenured Generation)和新生代(Young Generation)萌壳,在堆區(qū)之外還有一個(gè)代就是永久代(Permanet Generation)亦镶。老年代的特點(diǎn)是每次垃圾收集時(shí)只有少量對(duì)象需要被回收,而新生代的特點(diǎn)是每次垃圾回收時(shí)都有大量的對(duì)象需要被回收袱瓮,那么就可以根據(jù)不同代的特點(diǎn)采取最適合的收集算法缤骨。
a.年輕代回收算法(核心其實(shí)就是復(fù)制算法)
?HotSpot將新生代劃分為三塊,-塊較大的Eden空間和兩塊較小的Survivor空間尺借,默認(rèn)比例為8: 1: 1绊起。劃分的目的是因?yàn)镠otSpot采用復(fù)制算法來(lái)回收新生代,設(shè)置這個(gè)比例是為了充分利用內(nèi)存空間燎斩,減少浪費(fèi)虱歪。新生成的對(duì)象在Eden區(qū)分配(大對(duì)象除外,大對(duì)象直接進(jìn)入老年代) ,當(dāng)Eden區(qū)沒(méi)有足夠的空間進(jìn)行分配時(shí),虛擬機(jī)將發(fā)起一次Minor GC栅表。GC開(kāi)始時(shí),對(duì)象只會(huì)存在于Eden區(qū)和From Survivor區(qū)笋鄙,To Survivor區(qū)是空的(作為保留區(qū)域)。GC進(jìn)行時(shí)怪瓶,Eden區(qū)中所有存活的對(duì)象都會(huì)被復(fù)制到To Survivor區(qū),而在FromSurvivor區(qū)中,仍存活的對(duì)象會(huì)根據(jù)它們的年齡值決定去向萧落,年齡值達(dá)到年齡閥值(默認(rèn)為15 ,新生代中的對(duì)象每熬過(guò)一輪垃圾回收年齡值就加1 ,GC分代年齡存儲(chǔ)在對(duì)象的header中)的對(duì)象會(huì)被移到老年代中,沒(méi)有達(dá)到閥值的對(duì)象會(huì)被復(fù)制到To Survivor區(qū)。接著清空Eden區(qū)和From Survivor區(qū)洗贰,新生代中存活的對(duì)象都在To Survivor區(qū)找岖。接著, From Survivor區(qū)和To Survivor區(qū)會(huì)交換它們的角色,也就是新的To Survivor區(qū)就是上次GC清空的FromSurvivor區(qū)哆姻,新的From Survivor區(qū)就是.上次GC的To Survivor區(qū)宣增,總之,不管怎樣都會(huì)保證To Survivor區(qū)在一輪GC后是空的(其實(shí)這就是分代收集算法中的年輕代回收算法矛缨,稍后我們會(huì)看到)爹脾。GC時(shí)當(dāng)To Survivor區(qū)沒(méi)有足夠的空間存放上一次新生代收集下來(lái)的存活對(duì)象時(shí)帖旨,需要依賴(lài)?yán)夏甏M(jìn)行分配擔(dān)保,將這些對(duì)象存放在老年代中灵妨。
b.老年代回收算法(回收主要以Mark-Compact為主)
1.在年輕代中經(jīng)歷了N次垃圾回收后仍然存活的對(duì)象解阅,就會(huì)被放到年老代中。因此泌霍,可以認(rèn)為年老代中存放的都是一些生命周期較長(zhǎng)的對(duì)象货抄。
2.內(nèi)存比新生代也大很多(大概比例是1:2),當(dāng)老年代內(nèi)存滿(mǎn)時(shí)觸發(fā)Major GC即Full GC朱转,F(xiàn)ull GC發(fā)生頻率比較低蟹地,老年代對(duì)象存活時(shí)間比較長(zhǎng),存活率標(biāo)記高藤为。
c. 持久代(Permanent Generation)的回收算法
? ? ? ? 用于存放靜態(tài)文件怪与,如Java類(lèi)、方法等缅疟。持久代對(duì)垃圾回收沒(méi)有顯著影響分别,但是有些應(yīng)用可能動(dòng)態(tài)生成或者調(diào)用一些class,例如Hibernate 等存淫,在這種時(shí)候需要設(shè)置一個(gè)比較大的持久代空間來(lái)存放這些運(yùn)行過(guò)程中新增的類(lèi)耘斩。持久代也稱(chēng)方法區(qū),具體回收可見(jiàn)上文關(guān)于方法區(qū)的介紹桅咆。
六.常見(jiàn)垃圾收集器
首先我們先明確ScavengeGC (次收集)和Full GC(全收集)的區(qū)別
? ? ?? 新生代GC (Scavenge GC) :
? ? ? ?? Scavenge GC指發(fā)生在新生代的GC,因?yàn)樾律腏ava對(duì)象大多都是朝生夕死,所以Scavenge GC非常頻繁,般回收速度也比較快括授。當(dāng)Eden空間不足以為對(duì)象分配內(nèi)存時(shí),會(huì)觸發(fā)Scavenge GC轧邪。
? ? ? 一般情況下 ,當(dāng)新對(duì)象生成并且在Eden申請(qǐng)空間失敗時(shí),就會(huì)觸發(fā)Scavenge GC,對(duì)Eden區(qū)域進(jìn)行GC,清除非存活對(duì)象刽脖,并且把尚且存活的對(duì)象移動(dòng)到Survivor區(qū)。然后整理Survivor的兩個(gè)區(qū)忌愚。這種方式的GC是對(duì)年輕代的Eden區(qū)進(jìn)行,不會(huì)影響到年老代却邓。因?yàn)榇蟛糠謱?duì)象都是從Eden區(qū)開(kāi)始的硕糊,同時(shí)Eden區(qū)不會(huì)分配的很大,所以Eden區(qū)的GC是非常頻繁的。
? ? ? ? 老年代GC (Full GC/MajorGC) :
? ? ? ? ? Full GC指發(fā)生在老年代的GC,出現(xiàn)了Full GC一般會(huì)伴隨著至少一次的Minor GC (就是次收集搞不定的時(shí)候腊徙,才全收集)简十,比如:分配擔(dān)保失敗。Full GC的速度-般會(huì)比MinorGC慢10倍以上撬腾。當(dāng)老年代內(nèi)存不足或者顯式調(diào)用Systemgc()方法時(shí)螟蝙,會(huì)觸發(fā)Full GC。
下面正式看oracle公司提供的hotspot中7中垃圾收集器(有箭頭指向的民傻,是可以結(jié)合使用的)
新生代收集器
①Serial收集器(也叫串行收集器)
? ? ? ? 在JDK1.3之前唯一一個(gè)次收集器胰默,單線程收集器场斑,標(biāo)記和清理都是單線程,優(yōu)點(diǎn)是簡(jiǎn)單高效牵署,缺點(diǎn)是在進(jìn)行垃圾收集時(shí)必須暫停所有其他線程(“stop the world”,下圖的安全點(diǎn)就是來(lái)觸發(fā)暫停的)漏隐。是client級(jí)別默認(rèn)的GC方式,可以通過(guò)-XX:+UseSerialGC來(lái)強(qiáng)制指定奴迅。
②ParNew(也叫并行收集器)
? ? ? ? ParNew收集器其實(shí)是前面Serial 的多線程版本青责,除使多條線程進(jìn)行GC外,包括Serial可用的所有控制參數(shù)取具、收集算法脖隶、STW、對(duì)象分配規(guī)則暇检、回收策略等都與Serial完全一樣(當(dāng)老年代啟用CMS收集器XX: +UseConcMarkSweepG時(shí)浩村,ParNew是默認(rèn)新生代收集器).ParNew縮短了在垃圾回收時(shí)其他線程的暫停時(shí)間,但不能完全消除占哟。
? ? ? ? 由于存在線程切換的開(kāi)銷(xiāo)心墅,ParNew在單CPU的環(huán)境中比不上Serial,且在通過(guò)超線程技術(shù)實(shí)現(xiàn)的兩個(gè)CPU的環(huán)境中也不能100%保證能超越Serial.但隨著可用的CPU數(shù)量的增加,收集效率肯定也會(huì)大大增加榨乎。
③Parall Scavenge
? ? ? ? 與ParNew類(lèi)似Parall Scavenge也是使用復(fù)制算法怎燥,也是并行多線程收集器.但與其他收集器關(guān)注盡可能縮短垃圾收集時(shí)間不同,Parallel Scavenge更關(guān)注系統(tǒng)吞吐量:系統(tǒng)吞吐量=運(yùn)行用戶(hù)代碼時(shí)間運(yùn)行用戶(hù)代碼時(shí)間+垃圾收集時(shí)間)蜜暑,停頓時(shí)間越短就越適用于用戶(hù)交互的程序良好的響應(yīng)速度能提升用戶(hù)的體驗(yàn);而高吞吐量則適用于后臺(tái)運(yùn)算而不需要太多交互的任務(wù)可以最高效率地利用CPU時(shí)間盡快地完成程序國(guó)的運(yùn)算任務(wù). Parall Scavenge提供了如下參數(shù)設(shè)置系統(tǒng)吞吐量:
老年代收集器
①Serial Old收集器
老年代單線程收集器铐姚,Serial收集器的老年代版本,使用的是標(biāo)記-整理算法肛捍。
②Parallel Old收集器
Parallel Scavenge收集器的老年代版本隐绵,并行收集器,吞吐量?jī)?yōu)先拙毫,使用停止-復(fù)制算法依许。
③CMS收集器
這里我先插一嘴,并行和并發(fā)都是啥缀蹄,并行是幾個(gè)人同一時(shí)間干一件事峭跳,比如上面的Parallel Old收集器在垃圾回收時(shí)是幾個(gè)線程一起做的。并發(fā)是幾個(gè)人在同一時(shí)間能做不同的事缺前,也就是說(shuō)前面幾個(gè)垃圾收集器中蛀醉,用戶(hù)線程可以不暫停,系統(tǒng)一邊回收垃圾衅码,一邊執(zhí)行用戶(hù)線程拯刁。
CMS(Concurrent Mark Sweep)收集器是一款具有劃時(shí)代意義的收集器,一款真正意義上的并發(fā)收集器逝段,雖然現(xiàn)在已經(jīng)有了理論意義上表現(xiàn)更好的G1收集器垛玻,但現(xiàn)在主流互聯(lián)網(wǎng)企業(yè)線上選用的仍是CMS(如Taobag割捅、微店)。如果老年代使用CMS垃圾回收器夭谤,需要添加虛擬機(jī)參數(shù)-"XX:+UseConcMarkSweepGC"棺牧。
CMS是一-種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器(CMS又稱(chēng)多并發(fā)低暫停的收集器,基于”標(biāo)記清除”算法實(shí)現(xiàn)朗儒,整個(gè)GC過(guò)程分為以下4個(gè)步驟:
1.初始標(biāo)記(CMS initial mark)
2.并發(fā)標(biāo)記(CMS concurrent mark: GC Roots Tracing過(guò)程)
3.重新標(biāo)記(CMS remark)
4.并發(fā)清除(CMS concurrent sweep:已死象將會(huì)就地釋放,注意:此處沒(méi)有壓縮
其中1和3兩個(gè)步驟(初始標(biāo)記颊乘、重新標(biāo)記仍需STW但初始標(biāo)記僅只標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對(duì)象,速度很快;而重新標(biāo)記則是為了修正并發(fā)標(biāo)記期間因用戶(hù)程序繼續(xù)運(yùn)行而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那-部分對(duì)象的標(biāo)記記錄醉锄,雖然般比初始標(biāo)記階段稍長(zhǎng)乏悄,但要遠(yuǎn)小于并發(fā)標(biāo)記時(shí)間)
觸發(fā)條件
1.如果沒(méi)有設(shè)置?UseCMSInitiatingOccupancyOnly,虛擬機(jī)會(huì)根據(jù)收集的數(shù)據(jù)決定是否觸發(fā)(線上環(huán)境建議帶上這個(gè)參數(shù)恳不,不然會(huì)加大問(wèn)題排查的難度)
2.老年代使用率達(dá)到閾值?CMSInitiatingOccupancyFraction檩小,默認(rèn)92%
3.永久代的使用率達(dá)到閾值?CMSInitiatingPermOccupancyFraction,默認(rèn)92%烟勋,前提是開(kāi)啟?CMSClassUnloadingEnabled
4.新生代的晉升擔(dān)保失敗
內(nèi)容太多感興趣的可以看看大神的分析:http://www.cnblogs.com/aspirant/p/8663911.html
④G1收集器
內(nèi)容實(shí)在太多了规求,而且我沒(méi)完全理解,感興趣的可以看看大神講解
連接:http://www.cnblogs.com/aspirant/p/8663872.html
七.JVM調(diào)優(yōu)
劃重點(diǎn)了哈卵惦,這是我知道的為數(shù)不多的重點(diǎn)0.0