虛擬機是如何執行方法中的字節碼指令的。許多Java虛擬機的執行引擎在執行Java代碼的時候都有解釋執行(經過解釋器執行)和編譯執行(經過即時編譯器產生本地代碼執行)兩種選擇,在解釋執行時,虛擬機執行引擎是如何工做的。java
不管是解釋仍是編譯,也不管是物理機仍是虛擬機,對於應用程序,機器都不可能如人那樣閱讀、理解,而後就得到了執行能力。大部分的程序代碼到物理機的目標代碼或虛擬機能執行的指令集以前,都須要通過圖中的各個步驟。很容易就會發現圖中下面那條分支,就是傳統編譯原理中程序代碼到目標機器代碼的生成過程,而中間的那條分支,天然就是解釋執行的過程。現在,基於物理機、Java虛擬機,或者非Java的其餘高級語言虛擬機(HLLVM)的語言,大多都會遵循這種基於現代經典編譯原理的思路,在執行前先對程序源碼進行詞法分析和語法分析處理,把源碼轉化爲抽象語法樹(Abstract Syntax Tree,AST)。對於一門具體語言的實現來講,詞法分析、語法分析以致後面的優化器和目標代碼生成器均可以選擇獨立於執行引擎,造成一個完整意義的編譯器去實現,這類表明是C/C++語言。也能夠選擇把其中一部分步驟(如生成抽象語法樹以前的步驟)實現爲一個半獨立的編譯器,這類表明是Java語言。又或者把這些步驟和執行引擎所有集中封裝在一個封閉的黑匣子之中,如大多數的JavaScript執行器。緩存
Java編譯器輸出的指令流,基本上(使用「基本上」,是由於部分字節碼指令會帶有參數,而純粹基於棧的指令集架構中應當所有都是零地址指令,也就是都不存在顯式的參數。Java這樣實現主要是考慮了代碼的可校驗性)是一種基於棧的指令集架構(Instruction Set Architecture,ISA),指令流中的指令大部分都是零地址指令,它們依賴操做數棧進行工做。與之相對的另一套經常使用的指令集架構是基於寄存器的指令集,最典型的就是x86的二地址指令集,說得通俗一些,就是如今咱們主流PC機中直接支持的指令集架構,這些指令依賴寄存器進行工做。那麼,基於棧的指令集與基於寄存器的指令集這二者之間有什麼不一樣呢?架構
舉個最簡單的例子,分別使用這兩種指令集計算「1+1」的結果,基於棧的指令集會是這樣子的:性能
iconst_1優化
iconst_1spa
iaddcode
istore_0blog
兩條iconst_1指令連續把兩個常量1壓入棧後,iadd指令把棧頂的兩個值出棧、相加,而後把結果放回棧頂,最後istore_0把棧頂的值放到局部變量表的第0個Slot中。ip
若是基於寄存器,那程序可能會是這個樣子:內存
mov eax,1
add eax,1
mov指令把EAX寄存器的值設爲1,而後add指令再把這個值加1,結果就保存在EAX寄存器裏面。
瞭解了基於棧的指令集與基於寄存器的指令集的區別後,這兩套指令集誰更好一些呢?
應該這麼說,既然兩套指令集會同時並存和發展,那確定是各有優點的,若是有一套指令集全面優於另一套的話,就不會存在選擇的問題了。
基於棧的指令集主要的優勢就是可移植,寄存器由硬件直接提供,程序直接依賴這些硬件寄存器則不可避免地要受到硬件的約束。例如,如今32位80x86體系的處理器中提供了8個32位的寄存器,而ARM體系的CPU(在當前的手機、PDA中至關流行的一種處理器)則提供了16個32位的通用寄存器。若是使用棧架構的指令集,用戶程序不會直接使用這些寄存器,就能夠由虛擬機實現來自行決定把一些訪問最頻繁的數據(程序計數器、棧頂緩存等)放到寄存器中以獲取儘可能好的性能,這樣實現起來也更加簡單一些。棧架構的指令集還有一些其餘的優勢,如代碼相對更加緊湊(字節碼中每一個字節就對應一條指令,而多地址指令集中還須要存放參數)、編譯器實現更加簡單(不須要考慮空間分配的問題,所需空間都在棧上操做)等。棧架構指令集的主要缺點是執行速度相對來講會稍慢一些。全部主流物理機的指令集都是寄存器架構也從側面印證了這一點。雖然棧架構指令集的代碼很是緊湊,可是完成相同功能所需的指令數量通常會比寄存器架構多,由於出棧、入棧操做自己就產生了至關多的指令數量。更重要的是,棧實如今內存之中,頻繁的棧訪問也就意味着頻繁的內存訪問,相對於處理器來講,內存始終是執行速度的瓶頸。儘管虛擬機能夠採起棧頂緩存的手段,把最經常使用的操做映射到寄存器中避免直接內存訪問,但這也只能是優化措施而不是解決本質問題的方法。因爲指令數量和內存訪問的緣由,因此致使了棧架構指令集的執行速度會相對較慢。
看看在虛擬機中實際是如何執行的。
public int calc(){ int a=100; int b=200; int c=300; return(a+b)*c; }
從Java語言的角度來看,這段代碼沒有任何解釋的必要,能夠直接使用javap命令看看它的字節碼指令
public int calc(); Code: Stack=2,Locals=4,Args_size=1 0:bipush 100 2:istore_1 3:sipush 200 6:istore_2 7:sipush 300 10:istore_3 11:iload_1 12:iload_2 13:iadd 14:iload_3 15:imul 16:ireturn
javap提示這段代碼須要深度爲2的操做數棧和4個Slot的局部變量空間。
上面的執行過程僅僅是一種概念模型,虛擬機最終會對執行過程作一些優化來提升性能,實際的運做過程不必定徹底符合概念模型的描述……更準確地說,實際狀況會和上面描述的概念模型差距很是大,這種差距產生的緣由是虛擬機中解析器和即時編譯器都會對輸入的字節碼進行優化,例如,在HotSpot虛擬機中,有不少以「fast_」開頭的非標準字節碼指令用於合併、替換輸入的字節碼以提高解釋執行性能,而即時編譯器的優化手段更加花樣繁多。