Java知識(shí)之JVM

類(lèi)加載器
類(lèi)裝載器ClassLaoder負(fù)責(zé)加載class文件,class文件開(kāi)頭有特定的文件標(biāo)識(shí),將class文件字節(jié)碼內(nèi)容加載到內(nèi)存中,并將這些內(nèi)容轉(zhuǎn)換成方法區(qū)中的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu),并且ClassLoader只負(fù)責(zé)class文件的加載,至于它是否可以運(yùn)行,則有Execution Engine決定虛擬機(jī)自帶的類(lèi)加載器
- 引導(dǎo)類(lèi)加載器
- 這個(gè)類(lèi)加載使用C/C++與亞伯實(shí)現(xiàn)的,嵌套在JVM內(nèi)部,它用來(lái)加載Java的核心庫(kù)(JAVA_HOME/jre/lib/rt.jar、resources.jar或sun.boot.class.path路徑下的內(nèi)容),用于提供JVM自身需要的類(lèi)
- 并不繼承自java.lang.ClassLoader,沒(méi)有父加載器
- 加載擴(kuò)展類(lèi)和應(yīng)用程序類(lèi)加載器,并指定未他們的父類(lèi)加載器,處于安全考慮,Bootstrap啟動(dòng)類(lèi)加載器只加載包名未java、javax、sun等開(kāi)頭的類(lèi)
- 擴(kuò)展類(lèi)加載器
- Java語(yǔ)言編寫(xiě)
- 派生于ClassLoader類(lèi)
- 父類(lèi)加載器為啟動(dòng)類(lèi)加載器
- 從java.ext.dirs系統(tǒng)屬性所指定的目錄中加載類(lèi)庫(kù),或從JDK的安裝目錄的jre/lib/ext,子目錄下加載類(lèi)庫(kù)。如果用戶(hù)創(chuàng)建的JAR放在此目錄下,也會(huì)自動(dòng)由擴(kuò)展類(lèi)加載器加載
- 應(yīng)用類(lèi)加載器
java語(yǔ)言編寫(xiě)
派生于ClassLoader類(lèi)
父類(lèi)加載器為擴(kuò)展類(lèi)加載器
它負(fù)責(zé)加載環(huán)境變量classpath或系統(tǒng)屬性 java.class.path指定路徑下的類(lèi)庫(kù),改類(lèi)加載器是程序默認(rèn)的類(lèi)加載器 - 自定義加載器
堆
一個(gè)JVM實(shí)例只存在一個(gè)堆內(nèi)存,堆內(nèi)存的大小是可以調(diào)節(jié)的。類(lèi)加載器讀取了類(lèi)文件后,需要把類(lèi)、方法、常變量放到堆內(nèi)存中,保存所有引用類(lèi)型的真實(shí)信息,以方便執(zhí)行器執(zhí)行。堆邏輯上分為三部分:新生+養(yǎng)老+永久
新生區(qū)是類(lèi)的誕生、成長(zhǎng)、消亡的區(qū)域,一個(gè)類(lèi)在這里產(chǎn)生,應(yīng)用,最后被垃圾回收器收集、結(jié)束生命。新生區(qū)又分為兩部分,伊甸區(qū)和幸存者區(qū),所有的類(lèi)都是在伊甸區(qū)被new出來(lái)的。幸村區(qū)有兩個(gè):0區(qū)和1區(qū)。當(dāng)伊甸區(qū)的空間用完時(shí),程序又創(chuàng)建對(duì)象,JVM的垃圾回收器將對(duì)伊甸區(qū)的進(jìn)行垃圾回收(MinorGC),將伊甸園區(qū)中的不再被其他對(duì)象所引用的對(duì)象進(jìn)行銷(xiāo)毀,然后將將伊甸園區(qū)中的剩余對(duì)象移動(dòng)到幸存0區(qū)。若幸存0區(qū)也滿(mǎn)了,再對(duì)該區(qū)進(jìn)行垃圾回收,然后移動(dòng)到1區(qū).那如果1區(qū)也滿(mǎn)了呢?
再移動(dòng)到養(yǎng)老區(qū)。若養(yǎng)老區(qū)也滿(mǎn)了,那么這個(gè)時(shí)候?qū)a(chǎn)生MajorGC(FullGC),進(jìn)行養(yǎng)老區(qū)內(nèi)存清理。若養(yǎng)老區(qū)執(zhí)行了Full GC之后依然無(wú)法進(jìn)行對(duì)象的保存。就會(huì)產(chǎn)生OOM異常。
如果出現(xiàn)java.lang.OutofMemoryError:java heap space 異常,說(shuō)明Java虛擬機(jī)的堆內(nèi)存設(shè)置不夠。原因有二:
- (1)Java虛擬機(jī)的堆內(nèi)存設(shè)置不夠,可以通過(guò)參數(shù)-Xms,-Xmx來(lái)調(diào)整。
- (2)代碼中創(chuàng)建了大量大對(duì)象,并且長(zhǎng)時(shí)間不能被垃圾收集器收集(存在被引用),在Java8中,永久代已經(jīng)被移除,被一個(gè)稱(chēng)為元空間的區(qū)域所取代。元空間的本質(zhì)和永久代類(lèi)似
- 元空間和永久代之間最大的區(qū)別在于:
永久代使用的JVM的堆內(nèi)存,但是Java8以后的元空間并不在虛擬機(jī)中而是使用本機(jī)物理內(nèi)存,因此默認(rèn)情況下,元空間的大小僅受本地內(nèi)存限制。類(lèi)的元數(shù)據(jù)放入native memory,字符串池和類(lèi)的靜態(tài)變量放入Java堆中,這樣可以加載多少類(lèi)的元數(shù)據(jù)就不再由MaxPermSize控制,而由系統(tǒng)的實(shí)際可用空間來(lái)控制
Java虛擬機(jī)棧
棧管運(yùn)行,堆管存儲(chǔ)
- 棧也叫主內(nèi)存,主管Java程序的運(yùn)行,是線(xiàn)程創(chuàng)建時(shí)創(chuàng)建,它的生命周期是跟隨線(xiàn)程的生命周期,線(xiàn)程結(jié)束棧內(nèi)存也就釋放,對(duì)于棧來(lái)說(shuō)不存在垃圾回收問(wèn)題,只要線(xiàn)程一結(jié)束改棧就over,生命周期和線(xiàn)程一致,是線(xiàn)程私有的。8種基本類(lèi)型的變量+對(duì)象的引用變量+實(shí)例方法都是在函數(shù)的棧內(nèi)存中分配
- 棧存儲(chǔ)什么?
棧幀主要保存三類(lèi)數(shù)據(jù):
本地變量:輸入?yún)?shù)和輸出參數(shù)以及方法內(nèi)的變量(8中基本數(shù)據(jù)類(lèi)型,對(duì)象的引用地址),部分結(jié)果并參與方法的調(diào)用和返回
棧操作:記錄出棧、入棧的操作
棧幀數(shù)據(jù):包括類(lèi)文件、方法等等。 - 棧運(yùn)行原理:
每個(gè)方法執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀,用于存儲(chǔ)局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口等信息,每一個(gè)方法從調(diào)用直至執(zhí)行完畢的過(guò)程,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)中入棧到出棧的過(guò)程。棧的大小和具體JVM的實(shí)現(xiàn)有關(guān),通常在256k~756K之間
,約等于1Mb左右,不存在垃圾回收問(wèn)題
本地方法棧
方法區(qū)
供各線(xiàn)程共享的運(yùn)行時(shí)內(nèi)存區(qū)域。它存儲(chǔ)了每一個(gè)類(lèi)的結(jié)構(gòu)信息,例如運(yùn)行時(shí)的常量池、字段、和方法數(shù)據(jù)、構(gòu)造函數(shù)和普通方法的字節(jié)碼內(nèi)容。上面講的是規(guī)范,在不同的虛擬機(jī)里頭實(shí)現(xiàn)是不一樣的,最典型的就是永久代和元空間。
寄存器
記錄了方法之間的調(diào)用和執(zhí)行情況,類(lèi)似排班值日表,用來(lái)存儲(chǔ)指向下一條指令的地址,也即將要執(zhí)行的指令代碼,它是當(dāng)前線(xiàn)程所執(zhí)行的字節(jié)碼的行號(hào)指示器每個(gè)線(xiàn)程都有一個(gè)程序計(jì)數(shù)器,是線(xiàn)程私有的,就是一個(gè)指針,指到方法區(qū)中的方法字節(jié)碼(用來(lái)存儲(chǔ)指向下一條指令的地址,也即將要執(zhí)行的指令代碼),由執(zhí)行引擎讀取下一條指令,是一個(gè)非常小的內(nèi)存空間,幾乎可以忽略不記這塊內(nèi)存區(qū)域很小,它是當(dāng)前線(xiàn)程所執(zhí)行的字節(jié)碼的行號(hào)指示器,字節(jié)碼解釋器通過(guò)改變這個(gè)計(jì)數(shù)器的值來(lái)選取下一條需要執(zhí)行的字節(jié)碼指令如果執(zhí)行的是一個(gè)Native方法,那這個(gè)計(jì)數(shù)器是空的,用以完成分支、循環(huán)、跳轉(zhuǎn)、異常處理、線(xiàn)程恢復(fù)等基礎(chǔ)功能。不會(huì)發(fā)生內(nèi)存溢出(OOM) 錯(cuò)誤。
雙親委派機(jī)制的好處:
防止類(lèi)被重復(fù)加載
保護(hù)程序安全,防止核心API被隨意篡改
GC四大算法:
引用計(jì)數(shù)法:
每次對(duì)對(duì)象賦值時(shí)要維護(hù)引用計(jì)數(shù)器,且計(jì)數(shù)器本身也有一定的消耗較難處理循環(huán)引用,JVM的實(shí)現(xiàn)一般不采用這種方式
復(fù)制算法
年輕代中使用的MinorGC這種GC算法采用的是復(fù)制算法(Coping)
HOtSpot把年輕代分為了三部分:1個(gè)Eden區(qū)和2個(gè)Survivor區(qū)(分別叫from和to)。默認(rèn)比例8:1:1,一般情況下新創(chuàng)建的對(duì)象都會(huì)被分配到Eden區(qū)(一些大對(duì)象特殊處理),這些對(duì)象經(jīng)過(guò)第一次Minor GC后,如果仍然存活,將會(huì)被移到Survivor區(qū)。對(duì)象在Survivor區(qū)中每熬過(guò)一次Minor GC,年齡就會(huì)增加1歲,當(dāng)他的年齡增加到一定程度時(shí),就會(huì)被移動(dòng)到老年代中。因?yàn)槟贻p代中的對(duì)象基本都是朝生夕死(90%),所以在年輕代的垃圾回收算法使用的是復(fù)制算法,復(fù)制算法的基本思想就是將內(nèi)存分為兩塊,每次只用其中一塊當(dāng)這一塊內(nèi)存用完,就將還活著的對(duì)象復(fù)制到另外一塊上面。復(fù)制算法不會(huì)產(chǎn)生內(nèi)存碎片。
標(biāo)記清除
老年代一般是由標(biāo)記清除或者標(biāo)記清除與標(biāo)記整理的混合實(shí)現(xiàn),算法分成標(biāo)記和清除兩個(gè)階段,先標(biāo)記要回收的對(duì)象,然后統(tǒng)一回收這些對(duì)象,用通俗的話(huà)解釋一下標(biāo)記清除算法,就是當(dāng)程序運(yùn)行期間,若可以使用的內(nèi)存被耗盡的時(shí)候,GC線(xiàn)程就會(huì)觸發(fā)并將程序暫停,隨后將要回收的對(duì)象標(biāo)記一遍,最終統(tǒng)一回收這些對(duì)象,完成標(biāo)記清理工作接下來(lái)便讓?xiě)?yīng)用程序恢復(fù)運(yùn)行
優(yōu)點(diǎn):不需要額外空間
缺點(diǎn):兩次掃描耗時(shí)嚴(yán)重,會(huì)產(chǎn)生內(nèi)存碎片
標(biāo)記壓縮:
標(biāo)記/整理算法唯一的缺點(diǎn)就是效率也不高,不僅要標(biāo)記所有存活對(duì)象,還要整理所有存或?qū)ο蟮囊玫刂贰男噬蟻?lái)說(shuō),標(biāo)記/整理算法要低于復(fù)制算法,沒(méi)有最好的收集算法,只有最合適的收集算法分代收集算法
年輕代的特點(diǎn)是區(qū)域相對(duì)老年代較小,存活率低這種情況復(fù)制算法的回收整理速度是最快的。老年代的特點(diǎn)是區(qū)域大,對(duì)象存活率高,這種情況復(fù)制算法明顯變得不合適。一般是由標(biāo)記清除或標(biāo)記整理的混合實(shí)現(xiàn)

浙公網(wǎng)安備 33010602011771號(hào)