當(dāng)提到JVM類加載的時(shí)候,我們是在談什么?

當(dāng)我們提到JVM的時(shí)候,前提是我們知道啥是JVM,談這事的基礎(chǔ),至少知道它是java 虛擬機(jī)。此時(shí)至少要知道什么是虛擬機(jī),如果聽說過VM ware的話,需要知道這個(gè)VM是Virtual Machine的簡(jiǎn)稱,這樣就知道了JVM 是全稱是Java Virtual Machine。
那虛擬機(jī)是干啥的呢,用Java編寫的程序,計(jì)算機(jī)是沒法識(shí)別出來的,它根本就不懂這門語(yǔ)言,那么怎么辦?就要有角色給它翻譯翻譯,這個(gè)角色就是JVM,但是我們編寫的.java文件JVM也沒法直接識(shí)別,所以,需要把java程序編程成.class文件,JVM才能識(shí)別這個(gè)程序并且運(yùn)行它。JVM的好處體現(xiàn)在,無論我們使用的是什么操作系統(tǒng),只要有JVM環(huán)境存在,就可以運(yùn)行java程序。

1.生命周期

討論JVM生命周期的目的是,我們需要知道它是怎么來的,也需要知道它是怎么沒的。
簡(jiǎn)單的是:JVM隨著程序的運(yùn)行啟動(dòng),程序的結(jié)束而停止。
這里分為兩個(gè)線程,守護(hù)線程和普通線程。
守護(hù)線程體現(xiàn)在GC(垃圾回收),這個(gè)后面再說。守護(hù)線程是JVM自己的。普通的線程是運(yùn)行的程序的。

2.JVM內(nèi)存模型

這個(gè)比較基礎(chǔ)的是,大家都知道有:程序計(jì)數(shù)器、方法區(qū)、堆、虛擬機(jī)棧、本地方法棧、局部變量表、運(yùn)行時(shí)常量池。
其中具體在我這篇文章里有體現(xiàn):JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)域

3.JVM類加載

了解前面兩點(diǎn),就可以開始談?wù)搄vm類加載過程了。
剛剛也說了,代碼編譯后,會(huì)生成二進(jìn)制字節(jié)流文件(*.class)。
JVM把Class文件中的類描述數(shù)據(jù)加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)、準(zhǔn)備、解析、初始化,使這些數(shù)據(jù)最終成為可以被JVM直接使用的Java類型,這就叫做JVM的類加載機(jī)制。是不是并不復(fù)雜?
不過里面有一些名詞沒有解釋清楚。讓我們一起康康?。?震聲)


類加載過程

ok,我們逐步來說。

a.首先是加載

這是類加載過程的第一個(gè)階段,JVM在這個(gè)階段完成了三件事:
1.通過類的全限定名,獲取class文件。
2.把class文件代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu),放在方法區(qū)。
3.在內(nèi)存生成Class對(duì)象,代表這個(gè)類,作為方法區(qū)里這個(gè)類的訪問入口,這個(gè)Class對(duì)象雖然是對(duì)象,但是存在方法區(qū)中,而不是堆。

b.連接

連接分為三個(gè)小步驟:檢驗(yàn)、準(zhǔn)備、編譯。
1.驗(yàn)證:被加載的類結(jié)構(gòu)是否正確,保障安全性。
2.準(zhǔn)備:給類的靜態(tài)變量分配內(nèi)存(在方法區(qū)),并賦值,這個(gè)是默認(rèn)初始值。
3.解析:類的常量池內(nèi)的符號(hào)引用替換為直接引用。符號(hào)引用就是一組符號(hào)來描述目標(biāo),可以是任何字面量;直接引用就是直接指向目標(biāo)的指針、相對(duì)偏移量或一個(gè)間接定位到目標(biāo)的句柄。

c.初始化

初始化是給靜態(tài)變量設(shè)置正確的初始值。這里和前面準(zhǔn)備過程進(jìn)行區(qū)分,因?yàn)闇?zhǔn)備過程只是賦默認(rèn)初始值,這里才是設(shè)置正確的值。
值得注意的是以下情況,必須對(duì)類進(jìn)行初始化:
1.new 字節(jié)碼創(chuàng)建類的實(shí)例,或者調(diào)用靜態(tài)方法的時(shí)候,或者get static、put static讀取或設(shè)置靜態(tài)字段的時(shí)候。
2.反射調(diào)用的時(shí)候。
3.初始化一個(gè)類,如果其父類米有初始化,先觸發(fā)父類初始化。
4.虛擬機(jī)啟動(dòng)時(shí),指定的主類,比如包含main方法的類,虛擬機(jī)會(huì)初始化這個(gè)類。
5.使用jdk1.7的動(dòng)態(tài)語(yǔ)言支持時(shí),如果一個(gè)java.lang.invoke.MethodHandle實(shí)例最后的解析結(jié)果REF_getStatic、REF_putStatic、RE_invokeStatic的方法句柄,并且這個(gè)方法句柄對(duì)應(yīng)的類沒有進(jìn)行初始化,則需要先觸發(fā)其初始化。(這個(gè)之前我還真不知道~)
以上這幾種情況,被稱作“主動(dòng)引用”。

4.GC 垃圾回收

這里簡(jiǎn)單說一下前面提到的垃圾回收機(jī)制。當(dāng)然和JVM類加載關(guān)系倒是沒特別特別大。
有一篇我記錄的文章也談?wù)撨^這個(gè),詳細(xì)的可以看這個(gè):JVM垃圾收集器與內(nèi)存分配策略

先說哪些區(qū)域需要垃圾回收??為啥要回收內(nèi)存??
垃圾回收主要集中在堆和方法區(qū),這兩個(gè)區(qū)域內(nèi)存分配和回收是動(dòng)態(tài)的。回收內(nèi)存的意義在于空間是有限的,就算內(nèi)存特別大,也會(huì)有上限。
大家應(yīng)該也知道垃圾回收的兩種算法,一個(gè)是引用計(jì)數(shù),一個(gè)是可達(dá)性分析。引用計(jì)數(shù)算法由于存在兩個(gè)對(duì)象相互引用的可能性,并沒有在java中使用。
可達(dá)性分析算法,基本思想是通過GC Root的對(duì)象作為起點(diǎn),然后開始向下搜索。走過的路徑稱為引用鏈,當(dāng)一個(gè)對(duì)象到GC Root沒有引用鏈的話,此對(duì)象可以被回收了。

GC Root對(duì)象的選取條件包括:
1.棧(局部變量表)中引用的對(duì)象
2.方法區(qū)類靜態(tài)屬性引用的對(duì)象
3.方法區(qū)常量引用對(duì)象
4.本地方法棧中JNI引用的對(duì)象

引用狀態(tài)也分為四種:強(qiáng)引用、軟引用、弱引用、虛引用
強(qiáng)引用不參與垃圾回收,軟引用在內(nèi)存溢出前回收,弱引用垃圾回收的時(shí)候直接收走,虛引用也收走,回收時(shí)收到一個(gè)系統(tǒng)通知。
要知道的是,不可達(dá)的對(duì)象被標(biāo)記一次之后還能再搶救一下,如果被標(biāo)記兩次就直接GG了。具體是finalize方法,如果對(duì)象覆蓋這個(gè)方法,被標(biāo)記一下之后會(huì)被放入小黑屋——F-Queue,如果對(duì)象在finalize方法里面成功自救,關(guān)聯(lián)上GC Root,那他就不會(huì)被馬上回收,否則直接被干掉。

方法區(qū)里面廢棄常量和無用的類會(huì)被回收,至于什么是無用的類。
1.java堆里面不存在該類的實(shí)例
2.加載該類的ClassLoader已經(jīng)被回收
3.對(duì)象沒有被引用。
符合以上三點(diǎn),就是無用的類。

剛剛提到了標(biāo)記!這就要簡(jiǎn)單說一下垃圾收集算法。

  • 標(biāo)記-清除算法
  • 復(fù)制算法
  • 標(biāo)記-整理算法
  • 分代收集算法

1.標(biāo)記-清除算法
最基礎(chǔ)最簡(jiǎn)單,先掃一圈,標(biāo)記對(duì)象,然后再刪除,這種方法效率比較低下。而且清除之后產(chǎn)生了不連續(xù)的內(nèi)存碎片。以后分配大對(duì)象的時(shí)候,會(huì)以為空間不足,就會(huì)提前觸發(fā)垃圾回收動(dòng)作。
2.復(fù)制算法
效率比較高,將可用內(nèi)存分成兩塊,每次只用一塊,用完之后會(huì)把活著的對(duì)象扔到另外一塊區(qū)域。然后直接清理之前的區(qū)域。這個(gè)算法效率提升了,但是直接把內(nèi)存縮了一半。
3.標(biāo)記-整理算法
可以說是標(biāo)記-清除2.0,區(qū)別在于并沒有直接干掉對(duì)象,而是先標(biāo)記,然后讓存活的對(duì)象向一端進(jìn)行移動(dòng)。然后再清理。
4.分代收集算法
現(xiàn)在用的就是分代收集,把復(fù)制算法和標(biāo)記-整理進(jìn)行結(jié)合使用,我們知道根據(jù)對(duì)象的生命周期,可以分為新生代和老年代。
新生代的對(duì)象一般是大批量死去,少量存活,可以類比新陳代謝快,所有對(duì)于新生代使用復(fù)制算法,而且1:1很不科學(xué),現(xiàn)在是新生代內(nèi)存分一塊Eden和兩塊Survivor。
老年代的對(duì)象,存活率比較高,而且大多不易回收,新陳代謝慢,所以采用標(biāo)記清理算法。

然后執(zhí)行這些算法的就是收集器了,java這么多年發(fā)展,有很多收集器。全說比較費(fèi)勁,簡(jiǎn)述一下
1.Serial
最老的,采用復(fù)制算法,單線程會(huì)影響其他線程工作。
2.ParNew
Serial的多線程版本。
3.Parallel Scavenge
由于使用的依然是復(fù)制算法,它也依然是一個(gè)新生代收集器。Parallel Scavenge主要引入吞吐量這個(gè)概念,
吞吐量=運(yùn)行用戶代碼時(shí)間/(運(yùn)行用戶代碼時(shí)間+垃圾收集時(shí)間)。它提供了參數(shù)可以控制垃圾售后機(jī)停頓使勁和吞吐量。
4.Serial Old
Serial的老年代版本。標(biāo)記-整理算法
5.Parallel Old
Parallel Scavenge 老年代版本。
6.CMS(Conrrurent Mark Sweep)
目標(biāo)是獲取最短回收停頓時(shí)間,使用標(biāo)記-清除。這個(gè)可能會(huì)提前觸發(fā)Full GC
7.G1
這個(gè)比較NB,現(xiàn)在用的也是它。最主要的是支持了分代收集。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容