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