Java做爲一門編程語言可以得到如此普遍的承認,除了它有結構嚴謹,面向對象的編程語言以外,它還具有一個很是突出的特性:一次編寫
,處處運行
,即編寫的程序能夠擺脫硬件平臺束縛,它提供了一種相對安全的內存管理和訪問機制,避免了絕大部份內存泄漏和指針越界問題。java
談到jvm,就離不開與jdk和jre的對比,那麼它們之間到底有什麼區別和聯繫呢?編程
咱們先看這樣一幅架構圖,數組
從集合關係上看,jdk>jre>jvm
,除了範圍上的區別,咱們應該瞭解的是它們所包含的功能上的差異及各自發揮的做用。安全
jdk的全稱是Java Development kit
(java開發工具包),咱們能夠把程序設計語言
、java虛擬機
、java類庫
這三部分統稱爲jdk,jdk是用於支持java程序開發的最小環境
。Developer能夠很容易的使用裏面的方法以減小代碼量,裏面同時包含jre和一些開發的小工具(如編譯工具javac),同時包含了jre。markdown
jre的全稱是Java Running Environment
(java運行時環境 ),能夠把java類庫API中的javaSE的API子集
和java虛擬機
這兩部分統稱爲JRE,JRE是支持java程序運行的標準環境
。數據結構
jvm的全稱java virtual machine
(java 虛擬機),它只認識XXX.class文件
,虛擬機能夠識別這種文件的字節碼指令並調用操做系統上的API,正是這個緣由,java才能夠跨平臺使用
。架構
無論怎麼說,jvm終究是一個軟件
,那麼它是怎樣屏蔽底層的操做系統
、硬件
、CPU指令層
的細節呢?咱們以Java程序爲例來分析它的執行流程。併發
圖中Test.java
文件是按照java語法規則編寫的源文件,是一種高級語言,.java文件經javac編譯後就生成字節碼文件,字節碼文件是用於給java虛擬機執行用的,該文件的格式規範受到java虛擬機的定義。而jvm的目的就是將字節碼文件Test.class翻譯
爲操做系統及硬件的指令,便於在不一樣的操做系統上執行。jvm
NOTE: jvm虛擬機並非僅僅只針對java語言,像一些其它編程語言如Groovy
、Scala
和Kotlin
也能夠在jvm虛擬機上運行上,這些語言僅僅須要實現一個編譯器,經過該編譯器把源代碼文件編譯成JVM能識別的字節碼文件便可。編程語言
實現語言無關性的基礎是虛擬機和字節碼的存儲格式,Java虛擬機不與包括Java語言在內的任何程序語言綁定,它只與Class文件這種特定的二進制文件格式所關聯。
Class文件
是Java語言保持良好兼容性的關鍵,那麼Class文件的結構是什麼呢,存儲那些內容呢?
事實上,Class文件是一組以8字節爲基礎單位的二進制流
,各個數據項目嚴格的按照順序緊湊地排列在文件之中,中間沒有添加任何分割符,這使得整個Class文件存儲的內容幾乎所有是程序運行的必要數據,沒有空隙存在。
《Java虛擬機規範》規定了Class文件格式採用一種相似C語言結構體的僞結構來存儲數據,這種僞結構只包含兩種數據類型,即無符號數
和表
。
無符號數屬於基本
數據類型
,能夠用來描述數字
、索引引用
、數量值
或按照UTF-8編碼構成的字符串值
表是由多個無符號數或者其餘表做爲數據項構成的
複合數據類型
,爲了便於區分,全部表的命名的都以_info
結尾。
class文件經過固定的數據結構排列順序而且每種數據結構指定了佔用的字節長度來緊湊的在組成了完整的可讀文件,jvm只須要從文件開始的地方一步一步的讀取可以徹底的解析出這個類文件的內容。
來感覺一下字節碼文件長啥樣!
懵逼了吧,這都是些啥玩意,可是理解這些十六進制數字對咱們來講是很是有必要的,若是咱們能充分理解每個字節碼文件的細節,本身就能夠反編譯出Java源文件。在下面的學習中,咱們將一步步拆解class文件。
在class文件中,前4個字節被稱爲魔數
,它可以惟一肯定class文件可否被虛擬機接受。其實,魔數還普遍應用在GIF、JPEG等文件頭中。
緊接着魔數的4個字節存儲的是Class文件的版本號
,第5和第6個字節是次版本號
,第7和第8個字節是主版本號
。Java的版本號是從45開始的,JDK1.1以後的每一個JDK大版本發佈的主版本號加1(JDK1.01.1使用了45.045.3的版本號),《Java虛擬機規範》在Class文件校驗部分明確要求了即便文件格式並未發生變化,虛擬機也必須拒絕執行超過其版本號的Class文件,因此高版本的JDK能向下兼容之前版本的Class文件,可是不能運行之後版本的Class文件。
在魔數、版本號以後,下一個位置存儲的就是常量池
,常量池能夠認爲是Class文件裏的資源倉庫
,它是Class文件結構中與其它項目關聯最多的數據。常量池的前兩個字節佔有的位置稱爲常量池計數器
(constant_pool_cont),它記錄着常量池的組成元素常量池項
(cp_info)的個數。
常量池計數器是從1開始的,而不是從0開始的,即若是常量池計數器的值constant_pool_count=22
,則後面的cp_info的個數就爲21,這是由於在指定class文件規範的時候,將第0項常量空出來是爲了知足某些指向常量池的索引值的數據在特定的狀況下表達」不引用任何一個常量池項
「,這種狀況下能夠將索引值設置爲0來表示。
常量池中主要存放兩大類常量:字面量
和符號引用
,字面量能夠理解爲Java語言層面上的的常量
概念,如文本字符串
、被聲明爲final
的常量值等。而符號引用則包括類和結構的全限定名稱
、字段的名稱和描述符
、方法的名稱和描述符
等。
Class文件存儲了方法
、字段
等各類類信息,可是它僅僅是存儲了而已,它是不能反映出方法、字段等信息在內存中的佈局。這是由於Java語言並不像C++語言有連接的概念,可是Java語言在虛擬機加載時會進行動態的鏈接
,虛擬機將會從常量池中得到對應的符號引用
,再在類建立時或運行時進行解析
、翻譯
到具體的內存地址之中。
在常量池結束以後,緊接着的2個字節表明訪問標誌
(access_flags),這個標誌用於識別一些類或者接口層次的訪問信息
。好比標識一個Class是類仍是接口;是否認義爲public類型;是否認義爲abstract類型;是否被聲明爲final。具體的標誌位能夠如圖
標誌值與標誌名稱的對應關係以下:
標誌值 | 標誌名稱 |
---|---|
0x0001 | ACC_PUBLIC |
0x0010 | ACC_FINAL |
0x0020 | ACC_SUPER |
0x0100 | ACC_INTERFACE |
0x0200 | ACC_ABSTRACT |
0x1000 | ACC_SYNTHETIC |
0x2000 | ACC_ANNOTATION |
0x4000 | ACC_ENUM |
標誌名稱就是限定訪問信息的,如ACC_PUBLIC表示爲是否爲public類型,ACC_FINAL表示爲是否被聲明爲final,其它的標誌相似。
訪問標誌結束後,緊接就是索引
,包括類索引
、父類索引
與接口索引集合
,Class文件能夠由這三項數據來肯定該類型的繼承關係。咱們先了解下這三類索引的各有什麼做用
類索引用於肯定這個類的全限定名
,經過類的全限定名找到這個類,因此類索引的做用就是爲找出class文件所描述的這個類叫什麼名字。
父類索引用於肯定這個類的父類的全限定名
,有Java語言不支持多重繼承,因此除了Object外,其它類的父類索引只有一個。
它是用來描述這個類實現哪些接口
,因爲接口是多實現的,因此這些實現的接口將會按順序排列在索引集合中。接口索引的集合在入口處會有一個計數器
,它用來表示集合中索引的數量
,若是該類沒有實現接口,則該計數器爲0。
Note:類索引、父類索引和接口索引集合指向常量池中的符號引用。
字段表集合用於描述接口
或者類中聲明的變量
,它有若干個字段表組成,字段表集合的就相似一個數組的結構,jvm在編譯類的時候,會將類中的定義的字段的個數統計到字段計數器中,而後將每個字段信息以結構的形式組成起來放在字段計數器以後。其結構以下圖:
特別須要注意的是,這裏的字段包括類變量
以及實例變量
,可是不包括方法內部的聲明的局部變量。
咱們在思考這樣一個問題,字段表存儲的是那些信息,這些信息是什麼呢,事實上,字段表存儲的就是字段信息,咱們整理以下
既然字段有那麼多信息,他的存儲的形式是怎樣的呢?事實上,字段的存儲和咱們寫字段的形式是同樣的,不懂?那咱們就回顧下!
在字節碼,JVM定義了filed_info結構體來描述字段,它的形式也很簡單,就是一個結構體,
Field_info{
access_flags;
name_index;
descriptor_index;
attribute_count;
attributes;
}
複製代碼
access_flags
是訪問標誌,與前面講解的訪問標誌功能是相似的,緊接着access_flags標誌的是name_index
和descriptor_index
,它們是對常量池的引用,分別表明着字段的簡單名稱以及字段和方法的描述符。簡單名稱就是指沒有類型和參數修飾的方法或者字段名稱;字段和方法描述符指的是基本類型的頭一個大寫字母,如基本數據類型是byte,則方法描述修飾符是B。attribute_count
表示的屬性計數器,attributes
包含三部份內容(屬性名稱索引、屬性的長度和常量值索引)
方法表集合結構同字段表集合的結構是同樣的,咱們這裏主要講解它們之間的區別,剩下均可以按照屬性表集合來學習。
對於方法來講,volatile關鍵字和transient關鍵字是不修飾方法的,因此訪問標誌中不會有相應的標誌;可是synchronized、native、stricftp和abstract關鍵字是能夠修飾方法的,因此在會有相應的訪問標誌。
與字段相比,方法內是有代碼的,那麼方法內的代碼存儲到哪裏去了呢?事實上,對於方法裏的Java代碼,經Javac編譯器編譯成字節碼的指令後,存放在方法屬性表集合中會有一個名爲Code的屬性裏面。
Class文件的主要結構都說完了,咱們從宏觀的角度看看Class文件究竟是什麼樣,話很少說,來看圖
參考文獻
周志華.深刻理解JVM虛擬機
我是Simon郎
,一個想要天天都要博學的小青年,喜歡的話能夠關注
,轉發
、點贊
,期待在將來的日子裏你我共同進步!