JVM 加載 class 文件的原理機制----------【Java基礎】

問:你在寫Java代碼時,有沒有對代碼是如何轉換爲你想要的結果的。

答:這是eclipse中自帶的JVM,使其編譯成字節碼文件;所以Java代碼的運行過程也就是:編譯源碼文件---編譯字節碼文件---加載class文件---運行class字節碼文件;之後的兩個過程是在JVM中運行。

這裏寫圖片描述

 

問:那你知道如何在cmd命令窗口編譯Java文件嗎?而這個過程等於eclipse的什麼操作?

答:其實在命令窗口也就是輸入:javac 文件名.java;這樣就會生成三個.class文件,這三個文件分別爲:兩個類、一個接口;

而這個操作在eclipse中等於Ctr+s;

 

問:那你知道Java是如何實現跨平臺的?

答:Java之所以能跨平臺,很大一部分是得以字節碼文件的;這是因爲在不同的平臺上都使用這種程序存儲格式;進一步說就是只要是字節碼文件就可以了;

 

問:那JVM可以實現跨語言嗎?

答:額,這也是可以的;因爲如果非常熟悉字節碼的格式要求,就可以使用二進制編譯器自己寫一個符合要求的字節碼文件,之後交給JVM運行即可,這也就說可以把其他語言編寫的源碼編譯成字節碼文件,然後交給JVM運行。

一個字節碼文件:(參考)

這裏寫圖片描述

解釋:編譯自Student.java,編譯器的主版本號是52,也就是jdk1.8,這個類是public,然後是存放類中常量的常量池,各個方法的字節碼等。它存放了這個類的各種信息:字段、方法、父類、實現的接口等各種信息。

 

問:那你知道如何在命令窗口中運行class文件嗎?

答:其實也就是輸入:java 文件名;這就會啓動java虛擬機;加載該java的字節碼文件到內存中,之後就是運行內存中的字節碼指令了。

 

問:看來你對java的虛擬機比較瞭解,你能給我介紹下JVM的基本結構嗎?

答:

JVM基本結構如下圖所示:

這裏寫圖片描述

JVM劃分爲五部分了;

其中的方法區是用於存放類、接口的元數據信息的,加載進來的字節碼數據都存儲在這裏;之後也就到運行存放的字節碼文件了;這個部分也就是java棧;它是執行引擎運行字節碼時的運行時內存區,採用的是棧幀的形式保存每個方法的調用運行數據 。

剩餘的三部分也就是一些需要注意的地方;

本地方法棧:執行引擎調用本地方法時的運行時內存區。
Java堆(堆):運行時數據區,各種對象一般都存儲在堆上。
PC寄存器(程序計數器):功能如同CPU中的PC寄存器,指示要執行的字節碼指令。

 

其實JVM的功能模塊主要是:類加載器、執行引擎和垃圾回收系統。

 

問:看來你對JVM的基本結構瞭解的也不錯,給我詳細講下類加載器!

答:其實在java中的所有類,都必須被裝載到jvm中才能運行的;而裝載這個工作就是由jvm的類加載器完成的;而加載這個的本質就是把類文件從硬件讀取到內存中;其實類加載器也分種類,其有兩種裝載,一種爲隱式裝載,也就是在運行過程中碰到通過new等方式生成對象時,隱式調用類裝載器加載對應的類到jvm中。而另一種方式就是顯示裝載,就是通過class.forname()等方法,顯示加載需要的類。

而類加載具有動態性,該性體現在一個應用程序總是由n多個類組成,Java程序啓動時,並不是一次把所有的類全部加載後再 
運行,它總是先把保證程序運行的基礎類一次性加載到jvm中,其它類等到jvm用到的時候再加載。

之所以這樣做的原因就是節省內存的開銷;因爲java最早就是爲嵌入式系統而設計的,內存寶貴,這是一種可以理解的機制,而用到時再加載這也是java動態性的一種體現 。

其實類加載器就是類,只是功能是把類加載入JVM中;而且類加載器分爲三層結構,如下所示:

Bootstrap Loader  - 負責加載系統類 -----------JRE/lib/rt.jar
            | 
          - - ExtClassLoader  - 負責加載擴展類 -----------JRE/lib/ext或者java.ext.dirs指向的目錄
                          | 
                      - - AppClassLoader  - 負責加載應用類------------CLASSPATH環境變量, 由-
                        classpath或-cp選項定義,或者是JAR中的Manifest的classpath屬性定義.

至於爲什麼是三個類加載器,其實有兩方面的原因;一方面是爲了分工明確,另一方面也就是爲了實現委託模型。

 

問:看來你對類加載器很是瞭解,那你知道三個類加載器之間是如何協調工作的嗎?

答:其實這和類加載器的工作原理有一定的關係,因爲其工作原理又是基於三個機制:委託、可見性和單一性;

第一個的委託機制也就是當一個類加載和初始化的時候,類僅在有需要加載的時候被加載。假設你有一個應用需要的類叫作Abc.class,首先加載這個類的請求由 Application類加載器委託給它的父類加載器Extension類加載器,然後再委託給Bootstrap類加載器。Bootstrap類加載器 會先看看rt.jar中有沒有這個類,因爲並沒有這個類,所以這個請求由回到Extension類加載器,它會查看jre/lib/ext目錄下有沒有這 個類,如果這個類被Extension類加載器找到了,那麼它將被加載,而Application類加載器不會加載這個類;而如果這個類沒有被 Extension類加載器找到,那麼再由Application類加載器從classpath中尋找。記住classpath定義的是類文件的加載目 錄,而PATH是定義的是可執行程序如javac,java等的執行路徑。

第二個可見性機制其實也就是子類的加載器可以看見所有的父類加載器加載的類,而父類加載器看不到子類加載器加載的類。

第三個單一性機制就是根據這個機制,父加載器加載過的類不能被子加載器加載第二次。雖然重寫違反委託和單一性機制的類加載器是可能的,但這樣做並不可取。

 

問:你能給我說下類加載過程嗎?講了那麼多的類加載的知識。

答:類加載的話,也就分爲三個步驟,如下圖所示:

從上面的圖看,就會有一定疑問?爲什麼有了初始化過程還要驗證過程呢?這不就有點衝突嗎?

其實如果由編譯器生成的class文件,它肯定是符合JVM字節碼格式的;但是萬一有高手自己寫一個class文件,讓JVM加載並運行,用於惡意用途,就不妙了,因此這個class文件要先過驗證這一關,不符合的話不會讓它繼續執行的,也是爲了安全考慮吧。

這就是和Java安全性有關了。

而準備階段和初始化階段看似有點矛盾,其實不是的。因爲如果類中有語句:private static int a = 10。-------那麼它的執行過程是這樣的:首先字節碼文件被加載到內存後,先進行鏈接的驗證這一步驟,驗證通過後準備階段,再給a分配內存,又因爲變量a是static的,所以此時a等於int類型的默認初始值0,即a=0;然後到解析(後面在說),到初始化這一步驟時,才把a的真正的值10賦給a,所以此時a纔等於10。

 

問:那麼你知道類在什麼時候會被初始化?

答:其實有很多種情況的:

1)創建類的實例,也就是new一個對象
2)訪問某個類或接口的靜態變量,或者對該靜態變量賦值
3)調用類的靜態方法
4)反射(Class.forName("com.lyj.load"))
5)初始化一個類的子類(會首先初始化子類的父類)
6)JVM啓動時標明的啓動類,即文件名和類名相同的那個類
 

而且類的初始化也是分步驟的:

1)如果這個類還沒有被加載和鏈接,那先進行加載和鏈接。
2)假如這個類存在直接父類,並且這個類還沒有被初始化(注意:在一個類加載器中,類只能初始化一次),那就初始化直接的父類(不適用於接口)。
3)加入類中存在初始化語句(如static變量和static塊),那就依次執行這些初始化語句。

 

由於之前寫的,當時忘記記下參考資料。

 

要是哪裏寫的地方不對,還望多多指教。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章