class檔案格式
參考上一篇文章《【JVM故事】一個Java位元組碼檔案的誕生記》,后續還會專門講解class檔案的內部結構,
資料型別
jvm包括兩種資料型別,基本型別和參考型別,
基本型別包括,數值型別,boolean型別,和returnAddress型別,
數值型別包括,整型,浮點型,和char型別,
boolean型別同樣只有true和false,
returnAddress型別是一個指標,指向jvm指令的操作碼,在Java中沒有與之對應的型別,
boolean型別的操作會被轉化為int型別的操作進行,boolean陣列會當成byte陣列去操作,1表示true,0表示false,
參考型別包括三種,型別別,陣列型別,和介面型別,
它們的值是動態創建的類實體,陣列,或實作介面的類實體,
陣列有component型別和element型別,component型別就是陣列去掉最外層維度后剩下的型別,可能還是一個陣列型別(對于多維陣列),
element型別就是陣列里面存盤的最小資料的型別,它必須是一個基本型別,型別別,或介面型別,
對于一維陣列的話,component型別和element型別是相同的,
參考型別還有一個特殊值,就是null,表示沒有參考任何物件,
運行時公有資料區
堆
jvm有一個堆,在所有jvm執行緒間共享,堆是一個運行時資料區域,所有為類實體和陣列分配的記憶體都來自于它,
堆在jvm啟動時創建,堆中物件不用顯式釋放,gc會幫我們釋放并回收記憶體,
方法區
jvm有一個方法區,在所有jvm執行緒間共享,它存盤每一個類的結構,
像運行時常量池,欄位和方法資料,方法和建構式的代碼,還有特殊的方法用于類和實體的初始化,以及介面的初始化,
方法區在jvm啟動時創建,雖然方法區在邏輯上是堆的一部分,
但簡單實作時可以選擇不進行gc和壓縮,本規范沒有強制要求方法區的位置,也沒有要求管理已編譯代碼的策略,
運行時常量池
運行時常量池就是類或介面的位元組碼檔案里的常量池的運行時表示形式,它包含幾種常量,
如在編譯時就已經知道的數字字面量值,和必須在運行時決議的方法和欄位的參考,運行時常量池的功能類似于傳統語言的符號表,不過它包含的資料會更加寬泛,
運行時常量池分配在jvm的方法區,類或介面的運行時常量池在類或介面被jvm創建時才會構建,
運行時私有資料區
pc暫存器
jvm支持一次運行多個執行緒,每個執行緒都有自己的pc暫存器,任何時候一個執行緒只能運行一個方法的代碼,
如果方法不是native的,pc暫存器包含當前正在被執行的jvm指令地址,如果方法是native的,pc暫存器的值是未定義的,
jvm堆疊
每一個jvm執行緒都有一個私有的jvm堆疊,隨著執行緒的創建而創建,堆疊中存盤的是幀,
jvm堆疊和傳統語言如C的堆疊相似,保存區域變數和部分計算結果,參與方法的呼叫和回傳,jvm堆疊主要用于幀的出堆疊和入堆疊,除此之外沒有其它操作,
幀可能是在堆上分配的,所以jvm堆疊使用的記憶體不必是連續的,
native方法堆疊
native方法不是用Java語言寫的,為了支持它需要使用傳統堆疊,如C語言堆疊,不過jvm不能加載native方法,所以也不需要提供native方法需要的堆疊,
幀
每次當一個方法被呼叫時一個新的幀會被創建,當方法呼叫完成時,與之對應的幀會被銷毀,無論是正常完成還是拋例外結束,
所以幀是方法呼叫的具體體現形式,或稱方法呼叫是以幀的形式進行的,幀用來存盤資料和部分計算結果,和執行動態鏈接,方法回傳值,分發例外,
幀分配在創建幀的執行緒的jvm堆疊上,每一個幀都有自己的本地變數陣列,自己的操作資料堆疊,和一個對當前方法所在類的運行時常量池的參考,
本地變數陣列和運算元堆疊的大小在編譯時就確定了,它們隨著和幀關聯的方法編譯后的代碼一起被提供,因此幀這種資料結構的大小只依賴于jvm的實作,這些結構所需的記憶體可以在方法呼叫時同時被分配,
在一個執行緒執行的任何時刻,都只會有一個幀是處于激活的,這個幀被稱為當前幀,與之對應的方法被稱為當前方法,方法所在的類被稱為當前類,此時用到的本地變數陣列和運算元堆疊也都是當前幀的,
一個幀將不在繼續是當前幀,如果它的方法呼叫了另一個方法,或者它的方法結束了,
當一個方法被呼叫,一個新的幀被創建,當執行控制由原來的方法傳遞到新的方法時,這個新的幀變為當前幀,
當方法回傳時,當前幀把方法執行的結果傳回到上一幀,當上一幀被激活的同時當前幀會被丟棄,
本地變數陣列
每一幀都包含一個變數陣列,就是都熟知的本地變數存盤的地方,這個本地變數陣列的長度在編譯時確定,隨著編譯后的方法代碼一起提供,
通常一個本地變數(的位置)能夠存盤一個型別的值,但是long和double型別卻需要兩個本地變數(的位置)才能存一個值,
本地變數按索引尋址,第一個本地變數的索引是0,long和double需要消耗兩個連續的索引,但卻是按照較小的這個索引尋址的,不能按照較大的那個索引去讀資料,但是可以寫入,當然這樣將使本地變數內容錯亂,
在方法被呼叫時,jvm使用本地變數來接收傳遞進來的引數值,在類(靜態)方法呼叫時,所有引數被傳入從索引0開始的連貫的本地變數陣列里,
在實體(非靜態)方法呼叫時,索引0處總是傳入正在其上執行方法呼叫的那個物件的參考,(就是Java中的this了),所有引數被傳入從1開始的連貫的本地變數陣列里,
運算元堆疊
每個幀包含一個后進先出的堆疊,用于存盤正在執行的jvm指令的運算元,就是都熟知的運算元堆疊,這個堆疊的最大深度在編譯時就已確定,隨著編譯后的方法代碼一起提供,
當幀被創建時,運算元堆疊是空的,jvm提供一些指令用于加載常量值,本地變數值,欄位值到運算元堆疊上,另一些jvm指令采用運算元堆疊上的運算元進行操作,并把結果放回到運算元堆疊上,
運算元堆疊也用于準備將要傳遞給方法呼叫的引數和接收方法呼叫回傳的結果,
long和double型別的值占用兩個單位的堆疊深度,其它型別的值占用一個單位的堆疊深度,
動態鏈接
每一個幀都包含了對當前方法所屬型別的運行時常量池的參考,目的是為了支持方法代碼的動態鏈接,class檔案中描述一個方法參考被呼叫的方法和被訪問的變數的代碼,是采用符號參考的形式實作的,
符號參考的形式可以粗略的認為是字串的形式,就是用字串標明需要呼叫哪個類的哪個方法或訪問哪個欄位或變數,就像符號參考這個名字一樣,這些僅僅是符號,是拿不到具體值的,所以必須要進行轉換,
動態鏈接就是把這些符號方法參考轉換為具體的方法參考,在必要時加載類來決議尚未明確的符號,把符號變數的訪問轉換為這些變數運行時所在存盤結構的適合的偏移量(索引),這樣的方式又稱為后期系結,
方法呼叫
一個方法呼叫正常完成(即沒有拋例外)時,會根據所回傳的值的型別執行一個適合的return指令,當前幀會去恢復呼叫者的狀態,包括它的本地變數和運算元堆疊,使呼叫者的程式計數器適合的遞增來跳過剛剛的那個方法呼叫指令,
回傳值會被放到呼叫者幀的運算元堆疊上,然后繼續執行呼叫者方法的幀,
一個方法在呼叫時拋出了例外,且這個例外沒有在這個方法內被捕獲處理,將會導致這個方法呼叫的突然結束,這種情況下永遠不會向方法的呼叫者回傳一個值,
特殊方法
站在jvm的級別,每一個用Java寫的建構式都以一個實體初始化方法出現,且都是特殊的名字,就是<init>,這個名字是編譯器提供的,
實體初始化方法只能在jvm內部使用invokespecial這個指令呼叫,且只能在尚未初始化的類實體上呼叫,
一個類或介面最多可以有一個類或介面初始化方法,通過呼叫這個方法被初始化,類或介面的初始化方法也有特殊的名字,就是<clinit>,該方法沒有引數,且回傳值是void,
方法名稱也是由編譯器提供的,從Java7開始,在位元組碼中這個方法必須被標記為靜態的才行,
這個初始化方法是被jvm隱式呼叫的,它們絕對不會直接被用任何jvm指令呼叫,僅作為類初始化行程的一部分被間接的呼叫,
Java類別庫
jvm必須為Java類別庫的實作提供足夠的支持,一些類別庫中的類如果沒有jvm協助是無法實作的,
反射,就是在運行時獲取某個類的型別相關資訊,如它的欄位資訊,方法資訊,建構式資訊,父類資訊,實作的介面資訊,
這些資訊都必須是把一個類加載完之后才可以知道的,只有jvm才可以加載類,如java.lang.reflect這個包下的類和Class這個類,
在Java中加載一個類或介面用類加載器,即ClassLoader,背后還是委托給jvm來實作的,
鏈接和初始化一個類或介面,
安全,如java.security包下的類,還有其它類像SecurityManager,
多執行緒,如執行緒這個類Thread,
弱參考,像java.lang.ref包下的類,
公有設計,私有實作
以上內容只是jvm的一個“相對寬泛”的規范,它并不是實作方案,也不是實作細節,
實作者可以根據自身的需要來實作jvm,如運行在后端服務器上的jvm和運行在移動設備上的jvm肯定側重點有所不同,
從事Java的人都知道,事實上jvm是有較多的實作版本,
由于jvm是處在Java語言和作業系統之間的,所以它要向上提供對Java的支持,向下與作業系統良好互動,
寫在最后
高級語言(Java,C#)中的很多操作如檔案操作,網路操作,記憶體操作,執行緒操作,I/O操作等,都不是高級語言自身能夠實作的,
也不是它們的虛擬機(JVM,CLR)能夠實作的,實際最終是由作業系統實作的,因為這些都是系統資源,只有作業系統才有權限訪問,
如果你用Java或C#代碼創建了一個檔案,千萬不要以為是Java或C#創建了這個檔案,它們只是層層向下呼叫了作業系統的API,然后到檔案系統API,最后可能到磁盤驅動程式,
由此可以看出,要想設計一門語言,不單單是關鍵字、語法、編譯器,類別庫,虛擬機這些,還要深度了解作業系統,甚至是硬體,如CPU架構和CPU指令集等,
所以,和語言相關的事情,每一項都是例外的繁瑣復雜,都需要投入大量的人力、財力、時間去研究,最后即使研究成功了,可能沒有生態,沒人使用,自然也無法賺錢,
因此,國人現在還沒有一門屬于自己的真正語言,
>>> 熱門文章集錦 <<<
畢業10年,我有話說
【面試】我是如何面試別人List相關知識的,深度有點長文
我是如何在畢業不久只用1年就升為開發組長的
爸爸又給Spring MVC生了個弟弟叫Spring WebFlux
【面試】我是如何在面試別人Spring事務時“套路”對方的
【面試】Spring事務面試考點吐血整理(建議珍藏)
【面試】我是如何在面試別人Redis相關知識時“軟懟”他的
【面試】吃透了這些Redis知識點,面試官一定覺得你很NB(干貨 | 建議珍藏)
【面試】如果你這樣回答“什么是執行緒安全”,面試官都會對你刮目相看(建議珍藏)
【面試】迄今為止把同步/異步/阻塞/非阻塞/BIO/NIO/AIO講的這么清楚的好文章(快快珍藏)
【面試】一篇文章幫你徹底搞清楚“I/O多路復用”和“異步I/O”的前世今生(深度好文,建議珍藏)
【面試】如果把執行緒當作一個人來對待,所有問題都瞬間明白了
Java多執行緒通關———基礎知識挑戰
品Spring:帝國的基石
作者是作業超過10年的碼農,現在任架構師,喜歡研究技術,崇尚簡單快樂,追求以通俗易懂的語言解說技術,希望所有的讀者都能看懂并記住,下面是公眾號的二維碼,歡迎關注!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/170780.html
標籤:Java
上一篇:Elastic APM安裝
下一篇:redis 分布式鎖的簡單使用
