JVM的執(zhí)行程序詳解+內(nèi)存模型交互
什么是JVM
jvm它是一個(gè)虛構(gòu)出來的機(jī)器,但是它卻又是通過在實(shí)際的計(jì)算機(jī)上仿真模擬各種功能來實(shí)現(xiàn)的。jvm包含了一套字節(jié)碼的指令集,有一組寄存器,一個(gè)棧,一個(gè)垃圾回收堆,一個(gè)存儲方法域。JVM使得Java程序只需要生成在Java虛擬機(jī)上運(yùn)行代碼,就可以在多種平臺不加什么修改地運(yùn)行。JVM在執(zhí)行字節(jié)碼的時(shí)候,最終還是把字節(jié)碼解釋成機(jī)器指令執(zhí)行。
JDK、JRE、JVM有什么關(guān)系
「JDK:」 也就是開發(fā)者用來編譯,調(diào)試程序用的開發(fā)包,JDK也需要JAVA程序,需要在JRE上運(yùn)行。
「JRE:」 Java平臺,所有的Java程序都要在JRE的環(huán)境才可以運(yùn)行。
「JVM:」 它是JRE的一部分,是一個(gè)虛構(gòu)出來的計(jì)算器,是通過在實(shí)際的計(jì)算機(jī)來模擬計(jì)算機(jī)功能實(shí)現(xiàn)的。
JVM執(zhí)行程序的過程
一個(gè)Java文件從編碼開始到執(zhí)行需要經(jīng)過幾個(gè)階段:
1、編譯階段:首先.java文件經(jīng)過了Javac進(jìn)行編譯成了.class文件。
2、加載階段:緊接著.class文件經(jīng)過了類加載器加載到JVM的內(nèi)存當(dāng)中。
3、解釋階段:class字節(jié)碼經(jīng)過了字節(jié)碼解釋器解析成系統(tǒng)可以識別到的指令碼
4、執(zhí)行階段:向硬件設(shè)備發(fā)送指令碼來進(jìn)行操作。
「再細(xì)講一下每一個(gè)階段」
編譯階段
****類的編譯階段主要的目的就是把源碼文件編譯成為可以讓JVM解析的class文件,這個(gè)階段會經(jīng)過的詞法分析、語法的語義分析。
class文件包含了哪些內(nèi)容呢?
「Magic Number:」 這個(gè)是在.class文件頭的四個(gè)字節(jié),作用的話就是定義識別的標(biāo)準(zhǔn),只有符合了標(biāo)準(zhǔn)才可以被JVM解讀。
「版本號:」 編譯class文件的JDK版本號,這些版本是可以向下兼容的
「常量池:」 常量池里的信息主要有字面量、基本類型常量、和符號引用(類和接口全限定名,方法名和描述符等等)。
「訪問標(biāo)志:」 該類是不是接口、注釋、枚舉、模塊。
「類索引:」 類的索引、父類的索引、接口的索引集合,用于來確定類的繼承實(shí)現(xiàn)關(guān)系。
「字段表集合:」 這個(gè)是用于描述接口或者是類里聲明變量的信息,如(public/private/protected)。
「方法表集合:」 方法表集合跟字段表集合類似,也就是用來保存方法的相關(guān)信息,包括了方法的名稱
「屬性表集合:」 這里包括了類、方法、實(shí)例變量的指令碼。
加載階段
加載這個(gè)階段就是主要把.class文件加載到JVM內(nèi)存里,這個(gè)階段有裝載、連接、初始化這三個(gè)流程
「裝載:」 裝載階段呢就是把class里的信息讀取到內(nèi)存當(dāng)中去,首先是通過了類的全限名讀取到此類的二進(jìn)制流,緊接著把字節(jié)流里描述靜態(tài)結(jié)構(gòu)的信息轉(zhuǎn)化成為方法區(qū)里的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)。在加載階段的最后會在Java堆生成一個(gè)可以代表這個(gè)類的java.lang.class對象,作為了這個(gè)對象的訪問入口。
「連接:」 這個(gè)連接階段會進(jìn)行對class的信息來進(jìn)行驗(yàn)證,然后為類變量來分配內(nèi)存空間,并且賦予默認(rèn)值。首先是對class的內(nèi)容來驗(yàn)證字節(jié)是否符合了JVM的規(guī)范,然后為靜態(tài)的變量來分配內(nèi)存空間,最后進(jìn)行解析,把符號引用轉(zhuǎn)換成為直接引用,因?yàn)檫@里的類信息已經(jīng)在內(nèi)存當(dāng)中了,所以會把引用對象換成了對象在內(nèi)存里的實(shí)際地址。
「初始化:」 初始化階段主要是來執(zhí)行了初始化靜態(tài)塊的內(nèi)容,并且為靜態(tài)變量進(jìn)行真正的賦值。
解釋階段
解釋這個(gè)階段是在代碼執(zhí)行的期間觸發(fā)的,當(dāng)開始執(zhí)行一個(gè)類的方法的時(shí)候,首先是通過這個(gè)類的對象來作為入口,來找到相對應(yīng)的字節(jié)碼信息,然后再通過解釋器把字節(jié)碼解釋成指令碼。在最開始的執(zhí)行過程圖里有兩個(gè)解析器,解釋器有字節(jié)解釋器與即使編譯器JIT,一般的情況是運(yùn)行代碼的時(shí)候會使用的默認(rèn)字節(jié)碼解釋器來解析指令,只有是當(dāng)某一個(gè)方法是熱點(diǎn)方法,即使編譯器就會把熱點(diǎn)方法的指令碼進(jìn)行保存,等下次執(zhí)行的時(shí)候就不用重復(fù)的解析了,得以優(yōu)化。
執(zhí)行階段
操作系統(tǒng)把解釋器出來的指令碼,通過調(diào)用系統(tǒng)的硬件執(zhí)行最終的程序指令。
Java內(nèi)存間的交互操作
在Java的主內(nèi)存與工作內(nèi)存之間是如何來進(jìn)行具體的交互協(xié)議的呢?就是一個(gè)變量是怎么從主內(nèi)存拷貝到工作內(nèi)存的呢這一類細(xì)節(jié),在Java內(nèi)存模型中有八種操作,每一種操作都是原子的,不可再分的。
1、「lock(鎖定):」 鎖定這種操作作用于主內(nèi)存的變量,它會把一個(gè)變量標(biāo)記成為一條線程獨(dú)占的狀態(tài)
2、「unlock(解鎖):」 作用于主內(nèi)存的變量,把一個(gè)處于鎖定的變量釋放出來,釋放后的變量才可以被其他線程鎖定。
3、「read(讀取):」 作用于主內(nèi)存的變量,把一個(gè)變量從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存當(dāng)中,以便隨后的load使用。
4、「load(載入):」 作用于工作內(nèi)存的變量,它把read操作從主內(nèi)存中得到的變量值放入到工作內(nèi)存的變量副本當(dāng)中。
5、「use(使用):」 作用于工作內(nèi)存的變量,把工作內(nèi)存當(dāng)中的一個(gè)變量值傳遞給了執(zhí)行引擎
6、「assign(賦值):」 作用于工作內(nèi)存的變量,它把一個(gè)執(zhí)行引擎接受到的值賦給工作內(nèi)存的變量
7、「store(存儲):」 作用于工作內(nèi)存的變量,把工作內(nèi)存當(dāng)中的一個(gè)變量值傳送到主內(nèi)存當(dāng)中,以便隨后的write操作。
8、「write(寫入):」 作用于主內(nèi)存的變量,它把store操作從工作內(nèi)存中的一個(gè)變量的值傳送到主內(nèi)存的變量當(dāng)中。
上面的八種內(nèi)存交互操作必須滿足的規(guī)則
「第一、」 不允許read和load、store和write操作之一單獨(dú)出現(xiàn),即不允許一個(gè)變量從主內(nèi)存讀取了,但是工作內(nèi)存不接受,或者是從工作內(nèi)存發(fā)起了回寫了,但是主內(nèi)存不接受的情況出現(xiàn)。
「第二、」 不允許一個(gè)線程丟棄它的最近的assign操作,即變量在工作內(nèi)存當(dāng)中改變了之后就必須把該變化同步回主內(nèi)存。
「第三、」 不允許一個(gè)線程無原因地(沒有發(fā)生任何assign操作)把數(shù)據(jù)從線程的工作內(nèi)存同步回主內(nèi)存。
「第四、」 一個(gè)新的變量只能夠在主內(nèi)存“誕生”,不允許在工作內(nèi)存中直接使用一個(gè)未被初始化(load或assign)變量,換一句話來說就是對一個(gè)變量實(shí)施use、store操作之前,必須先執(zhí)行過了assign和load操作。
「第五、」 一個(gè)變量再次同一個(gè)時(shí)刻只允許一條線程對其進(jìn)行l(wèi)ock操作,但是lock操作可以被同一條線程重復(fù)執(zhí)行多次,多次執(zhí)行l(wèi)ock后,只有執(zhí)行相同次數(shù)的unlock操作,變量才回被解鎖。
「第六、」 如果對一個(gè)變量執(zhí)行l(wèi)ock操作,那將會清空工作內(nèi)存中此變量的值,在執(zhí)行引擎使用這個(gè)變量前,需要重新執(zhí)行l(wèi)oad或是assign操作初始化變量的值。
「第七、」 如果一個(gè)變量事先沒有被lock操作鎖定,那就不允許對它執(zhí)行unlock操作,也不允許去unlock一個(gè)被其他線程鎖定住的變量。
「第八、」 對一個(gè)變量執(zhí)行unlock操作之前,必須先把此變量同步回主內(nèi)存中(執(zhí)行store、write操作)。
Volatile變量的特殊規(guī)則
當(dāng)變量被定義成volatile之后,保證此變量對所有線程的可見性,這里所說的可見性是指當(dāng)一條線程修改了這個(gè) 變量的值,新值的話對于其他的線程來說是可以馬上得知的,普通的變量不可以做到這一點(diǎn),因?yàn)槠胀ㄗ兞康闹翟诰€程里的傳遞時(shí)均需要通過主內(nèi)存來完成。
Java內(nèi)存模型里對volatile變量定義的特殊規(guī)則有:
(1)線程對變量的load、read的操作需要連續(xù)的并且一起出現(xiàn)的,要求是在工作內(nèi)存當(dāng)中,每次使用變量的時(shí)候都必須要先從主內(nèi)存刷新最新的值,這也保證能看見其他線程對變量所做的修改。
(2)線程對變量store、write操作需要連續(xù)的并且是一起出現(xiàn)的,要求是在工作內(nèi)存當(dāng)中,每一次修改變量后都必須立刻的同步回主內(nèi)存當(dāng)中,用于保證其他線程可以看到自己對變量V所做的更改。