前言
1. class文件與dex文件解析
在學(xué)習(xí)java虛擬機(jī)之前,我們有必要先來了解下下class文件與dex文件。相比大家對這兩文件都耳熟能詳,但是對于初學(xué)者來說卻是"聽起聲而不見其人"。下面我們就來了解下。首先我們先來對他們有個(gè)總體的概念:
- class文件:能夠被JVM識(shí)別,加載并執(zhí)行的文件格式。
- dex文件 :能夠被DVM(Dalvik-Android平臺(tái)虛擬機(jī))識(shí)別,加載并執(zhí)行的文件格式。
這就是兩者大致的作用與概念。下面我們來分別了解下。
1.1 class文件
-
class文件的概括
對于class文件的概念,上面我們提到了,他就是一種文件格式。是一種能被Java虛擬機(jī)識(shí)別的文件格式,所以在了解虛擬機(jī)之前有必要先了解下class文件。Java程序的執(zhí)行依賴于編譯環(huán)境和運(yùn)行環(huán)境,下面我們用一張圖來了解下流程的完成。

此圖來源
-
生成class文件
相信在學(xué)習(xí)java的時(shí)候老師都是要求我們用記事本來編寫java代碼,然后用命令生成.class文件,然后我們就可以用java命令去執(zhí)行我們別寫的代碼了。其實(shí)這個(gè)過程就對應(yīng)了我們上面的流程圖過程。不過對于生成.class文件的方式有很多。常見有如下:
- 通過IDE自動(dòng)幫我們build(我們現(xiàn)實(shí)中使用的方法)
- 利用命令生成
為了更好的理解與回顧基礎(chǔ),我們在走一遍2.這個(gè)流程。
- 編寫java文件:
Hellow.java:
public class Hellow {
public static void main(String args[]){
System.out.println("Hellow Android!\n");
}
}
- 這段代碼非常簡單。下面我們通過命令javac語言編譯器:
javac Hellow.java
通過在命令行輸入這段命令。我們就可以生成Hellow.class文件。這時(shí)我們就可以通過java命令:
java Hellow
后接我們的類名來運(yùn)行這個(gè).class文件來執(zhí)行我們的代碼。最后輸出入下:

現(xiàn)在我們回頭看看開頭的總結(jié)圖。就更加明了了.class文件是什么了。
-
class文件結(jié)構(gòu)
在明白了什么是class文件之后我們就來class文件的格式與結(jié)構(gòu)。
首先我們要知道一個(gè)class文件它的作用,即:記錄一個(gè)類文件的所有信息,記住是所有,他與源文件相比更復(fù)雜。下面我們來具體看下他的結(jié)構(gòu):
- 一種8位二進(jìn)制的一種流文件
- 各個(gè)數(shù)據(jù)按順序緊密的排列,無間隙(好處是減小文件體積,使JVM讀取更迅速)
- 每個(gè)類和接口都單獨(dú)占用一個(gè)class文件(方便管理自己)
下面我們就來了解下class文件結(jié)構(gòu)類型:

這張圖就是一個(gè).class文件包含的所有類型。這里面比較中的要的就常量池。我們的主要內(nèi)容都包含在其中。那么constant_pool又有哪些類型呢?我們來簡單列舉下:
簡單類型(基礎(chǔ)數(shù)據(jù)類型常量):
CONSTATN_Integer_info
CONSTATN_String_info
CONSTATN_Long_info
.
.
.
等等
復(fù)雜類型:CONSTATN_Class_info(記錄class信息)
CONSTATN_Fildref_info(記錄所有成員變量)
-
CONSTATN_Methodref_info(記錄所有方法信息)
.
.
.
等等
最后這些復(fù)雜類型都會(huì)包含簡單類型。最后指向(通過下標(biāo)索引指向)具體的常量池常量。這是.class文件的內(nèi)部結(jié)構(gòu)具體是不是這樣的我們通過010Editor軟件查看如下:
圖片.png
此圖為打開二進(jìn)制.class文件的結(jié)構(gòu)圖。從圖中可以看到,一個(gè).class文件正是包含上面我們所列舉出來的類型。其中常量池(constant_pool)所占最多,而他的類型(Comment)可以看到正是我們列舉出來的。我們打開看下:
圖片.png
我們以cp_info(結(jié)構(gòu)體類型) constant_pool(常量池)為例。 它里面分別包含tag(標(biāo)記) class_index(表明方法所屬那個(gè)類) name_and_type_index(方法名與類型) 最后根據(jù)索引可以找到他具體的內(nèi)容。這里我們只需大概有個(gè)了解,更詳細(xì)講解查考Class文件內(nèi)容解析 class文件弊端
對于class文件結(jié)構(gòu)大家可能云里霧里。不過大家只需了解就好,待以后深入了解后再慢慢理解。那么他有什么弊端呢?如下:
- 內(nèi)存占用大,不適合移動(dòng)端
- 堆棧的加棧模式,加載速度慢
- 文件IO操作多,類查找慢
基于class文件的這些缺點(diǎn),在移動(dòng)端就尤為明顯。以為我們的移動(dòng)設(shè)備最寶貴的就是內(nèi)存,因此dex由此誕生,下面我們來理解下dex文件
1.2 dex文件
基于class的缺點(diǎn),在Android平臺(tái)移動(dòng)設(shè)備上我們運(yùn)行在Dalvik即為dex文件。他的作用為記錄整個(gè)工程所有類文件的的信息。記住是整個(gè)工程。下面我們簡單了解下如何生成 :

dex文件結(jié)構(gòu):
- 一種8位二進(jìn)制的一種流文件
- 各個(gè)數(shù)據(jù)按順序緊密的排列,無間隙
- 整個(gè)應(yīng)用中的所有java源文件都放在一個(gè)dex中
可以看到他與class文件只有最后一點(diǎn)不同。 下面我們來具體看下。
dex文件結(jié)構(gòu):

從圖中我們看到分三個(gè)區(qū)。文件頭:描述了dex文件的基本信息(所有字段大致的分布等);索引區(qū):包含所有相關(guān)信息的索引;數(shù)據(jù)區(qū):索引所指向的位置即在數(shù)據(jù)區(qū)存儲(chǔ),即索引區(qū)盡量位置,數(shù)據(jù)區(qū)記錄位置所指向的具體數(shù)據(jù)。這里面的鏈接數(shù)據(jù)區(qū)即為我們的動(dòng)態(tài)鏈接庫.so文件。下面我們分別對3本分講解。
dex文件頭:

總結(jié): dex文件頭記錄dex文件大小以及各個(gè)區(qū)段的起始位置和長度(偏移量)
索引區(qū):

從這幅圖中我們可以看到。第一行為頭文件,以ids為結(jié)尾都是索引區(qū),索引區(qū)所指引的值就是value位置中的值。里面包含了所有類的方法,類,以及常量等索引以及集體的數(shù)據(jù)。
1.3 兩者的異同
- 本質(zhì)上他們都是一樣的。dex是從class文件演變而來的
- class文件存在許多冗余信息,dex會(huì)取出冗余,并整合。
針對第2點(diǎn)。我們可以從這幾方面理解。首先,class文件時(shí)一個(gè)類或者接口為一個(gè)class文件而dex是所有文件合并成一個(gè)dex文件,比我們從圖中的結(jié)構(gòu),也看直觀的看到dex文件要比class文件更簡潔,所以當(dāng)類越來越多則class文件也會(huì)越來越多,而dex文件會(huì)將多個(gè)class文件整合到一個(gè)dex文件。如下圖:
圖片.png
在class文件越來越多的時(shí)候 dex文件的3大區(qū)都不會(huì)增加。而.class文件會(huì)隨著文件增多,每個(gè)文件的結(jié)構(gòu)又獨(dú)立,所以一定會(huì)產(chǎn)生很多冗余重復(fù)數(shù)據(jù)。
3. Java虛擬機(jī)
理解JVM我們先來看下他的結(jié)構(gòu):

詳細(xì)結(jié)構(gòu):

2個(gè)圖表達(dá)的含義是一樣的。
從圖中可以分為三部分。第一部分類加載器子系統(tǒng)將class文件進(jìn)行加載,第二部分類加載器將加載后class字節(jié)碼數(shù)據(jù)存放在內(nèi)存空間,JVM將內(nèi)存空間分成5部分,同時(shí)我們的垃圾回收機(jī)制(gc)也是回收這部分的內(nèi)存占用。第三部分比較底層,是與一些系統(tǒng)CPU交互等。所以第三部分我們占不做分析。
3.1 將.class文件載入類加載器
這部分主要就是講.java源文件轉(zhuǎn)化為.class字節(jié)碼文件。如何生成前面已經(jīng)提過。具體細(xì)節(jié)如下:

這些生成都是javac來完成的 javac就是java編譯器
3.2 類加載器
3.1.2 類加載器有哪些
類加載指將類的字節(jié)碼文件(.class)中的二進(jìn)制數(shù)據(jù)讀入內(nèi)存,將其放在運(yùn)行時(shí)數(shù)據(jù)區(qū)的方法區(qū)內(nèi),然后在堆上創(chuàng)建java.lang.Class對象,封裝類在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)類裝載器子系統(tǒng)負(fù)責(zé)查找并裝載類型信息。其實(shí)Java虛擬機(jī)有兩種類裝載器:系統(tǒng)裝載器(前兩個(gè))和用戶自定義裝載器(后兩個(gè))。前者是Java虛擬機(jī)實(shí)現(xiàn)的一部分,后者則是Java程序的一部分。

- 啟動(dòng)類加載器(BootstrapClassLoader):在JVM運(yùn)行時(shí)被創(chuàng)建,負(fù)責(zé)加載存放在JDK安裝目錄下的jre\lib的類文件
- 擴(kuò)展類加載器(Extension ClassLoader):該類加載器負(fù)責(zé)加載JDK安裝目錄下的\jre\lib\ext的類
- 應(yīng)用程序類加載器(AppClassLoader):負(fù)責(zé)加載用戶類路徑(Classpath)所指定的類,開發(fā)者可以直接使用該類加載器,如果應(yīng)用程序中沒有定義過自己的類加載器,該類加載器為默認(rèn)的類加載器。
- 用戶自定義類加載器(User ClassLoader):開發(fā)人員通過拓展ClassLoader類定義的自定義加載器,加載程序員定義的一些類。
類加載指將類的字節(jié)碼文件(.class)中的二進(jìn)制數(shù)據(jù)讀入內(nèi)存,將其放在運(yùn)行時(shí)數(shù)據(jù)區(qū)的方法區(qū)內(nèi),然后在堆上創(chuàng)建java.lang.Class對象,封裝類在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)。和其它對象一樣,用戶自定義的類裝載器以及Class類的實(shí)例放在內(nèi)存中的堆區(qū),而裝載的類型信息則位于方法區(qū)。
-
委派模式(Delegation Mode)
它是JVM加載類的模式(向上查找,向下加載)。當(dāng)JVM加載一個(gè)類的時(shí)候,下層的加載器會(huì)將將任務(wù)委托給上一層類加載器,上一層加載檢查它的命名空間中是否已經(jīng)加載這個(gè)類,如果已經(jīng)加載,直接使用這個(gè)類。如果沒有加載,繼續(xù)往上委托直到頂部。檢查完了之后,按照相反的順序進(jìn)行加載,如果Bootstrap加載器找不到這個(gè)類,則往下委托,直到找到類文件。對于某個(gè)特定的類加載器來說,一個(gè)Java類只能被載入一次,也就是說在Java虛擬機(jī)中,類的完整標(biāo)識(shí)是(classLoader,package,className)。一個(gè)類可以被不同的類加載器加載。
如果我們將這個(gè)類放到JAVA_HOME/jre/lib/ext這個(gè)路徑中去(相當(dāng)于交給Extension加載器加載),按照同樣的規(guī)則,最后由Extension加載器加載MyClass類。此類一共加載2次。但是每次都是由不同的ClassLoader完成。
總結(jié): 1和2加載器是加載加載jdk相關(guān)的class文件。3是加載應(yīng)用用到的class文件,4是我們自定義類加載器,可以加載指定的class文件。
3.1.3 加載流程
類加載器除了要定位和導(dǎo)入二進(jìn)制class文件外,還必須負(fù)責(zé)驗(yàn)證被導(dǎo)入類的正確性,為類變量分配并初始化內(nèi)存,以及解析符號引用。這些動(dòng)作還需要按照以下順序進(jìn)行:

3.2 運(yùn)行時(shí)數(shù)據(jù)區(qū)(也就是內(nèi)存管理)
這部分是整合JVM的核心,也是GC垃圾回收的的主要部分,所以單獨(dú)在下一篇講解。
3.3 執(zhí)行引擎
類加載器將字節(jié)碼載入內(nèi)存之后,執(zhí)行引擎以Java字節(jié)碼指令為單元,讀取Java字節(jié)碼。問題是,現(xiàn)在的java字節(jié)碼機(jī)器是讀不懂的,因此還必須想辦法將字節(jié)碼轉(zhuǎn)化成平臺(tái)相關(guān)的機(jī)器碼。這個(gè)過程可以由解釋器來執(zhí)行,也可以有即時(shí)編譯器(JIT Compiler)來完成。

優(yōu)秀文章推薦(本文部分參考)
Java虛擬機(jī)工作原理
理解Java虛擬機(jī)體系結(jié)構(gòu)
Java虛擬機(jī)工作原理詳解
書籍:《深入理解java虛擬機(jī)》(有需要PDF請留言,不過希望大家支持正版)


