深入了解java虛擬機(JVM) 第十三章 虛擬機字節碼執行引擎
一、概述
執行引擎是java虛擬機最核心的組成部件之一。虛擬機的執行引擎由自己實現,所以可以自行定制指令集與執行引擎的結構體系,並且能夠執行那些不被硬件直接支持的指令集格式。所有的Java虛擬機的執行引擎都是一致的:輸入的是字節碼文件,處理過程是字節碼解析的等效過程,輸出的是執行結果。本節將主要從概念模型的角度來講解虛擬機的方法調用和字節碼執行。
二、運行時棧幀結構
1.什麽是棧幀
棧幀也叫過程活動記錄,是編譯器用來進行方法調用和方法執行的一種數據結構,它是虛擬機運行是數據區域中的虛擬機棧的棧元素。
棧幀包括了局部變量表、操作數棧、動態鏈接、方法返回地址和額外的一些附加信息。
在編譯過程中,局部變量表的大小已經確定,操作數棧深度也已經確定,因此棧幀在運行的過程中需要分配多大的內存是固定的,不受運行時影響。
對於的有逃逸的對象也會在棧上分配內存,對象的大小其實在運行是也是確定的,因此即使出現了棧上分配,也不會改變棧幀大小。
棧幀的結構圖如下:
一個線程中,可能調用鏈會很長,很多方法都同時處於執行狀態。對於執行引擎來講,活動線程中,只有棧頂的棧幀是最有效的,稱為當前棧幀,這個棧幀所關聯的方法稱為當前方法。執行引擎所運行的字節碼指令反對當前棧幀進行操作。
2.局部變量表
局部變量表是一組變量值存儲空間,用於存放方法參數和方法內定義的局部變量。局部變量表的容量以變量槽(Variable Slot)為最小單位。 一個Slot可以存放一個32位以內(boolean、byte、char、short、int、float、reference和returnAddress)的數據類型,reference類型表示一個對象實例的引用,returnAddress已經很少見了,可以忽略。
需要註意的是:
1)局部變量表沒有初始值,要使用局部變量,必須先賦值
2)slot復用問題。當一個變量的PC寄存器的值大於slot的作用域的時候,slot是可以復用的,這樣的話就會導致JVM難以回收,所以在使用對象完後,要把對象指定為null,這樣才會方便垃圾回收。
3.操作數棧
操作數棧(Operand Stack) 也常稱為操作棧,它是一個後入先出棧。當一個方法執行開始時,這個方法的操作數棧是空的,在方法執行過程中,會有各種字節碼指令往操作數棧中寫入和提取內容,也就是 出棧/入棧操作。
在概念模型中,一個活動線程中兩個棧幀是相互獨立的。但大多數虛擬機實現都會做一些優化處理:讓下一個棧幀的部分操作數棧與上一個棧幀的部分局部變量表重疊在一起,這樣的好處是方法調用時可以共享一部分數據,而無須進行額外的參數復制傳遞。
4.動態連接
指向該棧所屬的方法。
5.方法返回地址
方法調用時通過一個指向方法的指針指向方法的地址,方法返回時將回歸到調用處,那個地方就是返回地址。
6.附加信息
虛擬機規範中允許具體的虛擬機實現增加一些規範裏沒有描述的信息到棧幀中。這部分信息完全取決於虛擬機的實現。
三、方法調用
方法調用不等同於方法的執行,方法調用階段的唯一任務就是確定被調用方法的版本。
方法調用階段的目的:確定被調用方法的版本(哪一個方法),不涉及方法內部的具體運行過程,在程序運行時,進行方法調用是最普遍、最頻繁的操作。一切方法調用在Class文件裏存儲的都只是符號引用,這是需要在類加載期間或者是運行期間,才能確定為方法在實際 運行時內存布局中的入口地址(相當於之前說的直接引用)。
1.解析
在類加載的解析階段,會將其符號引用轉化為直接引用(入口地址),這類方法的調用稱為“解析(Resolution)”。
在java中的解析調用:
1)靜態方法
2)構造器方法
3)私有方法
4)final修飾的方法
2.靜態分配調用(方法的重載)
1)方法調用選擇靜態類型的方式稱為靜態分派,看下面一個例子
package com.example.demo; public class Demo { /** * * 創建兩個類 * */ static class Parent{} static class Son extends Parent{} /** * * 對show方法進行兩種不同參數的重載 */ public static void show(Parent p) { System.out.println("這是Parent方法"); } public static void show(Son s) { System.out.println("這是Son方法"); } public static void main(String[] args) { //創建了一個Parent類,他的實例P的靜態類型和實際類型都是Parent Parent p = new Parent(); //令p等於Son類型,此時P的靜態類型為Parent,實際類型都是Son p = new Son(); //方法重載時,方法調用選擇的是靜態類型,所以此時調用的是Parent參數 show(p); //將P的靜態類型強制轉化為Son,此時P的靜態類型為Son,實際類型都是Son //方法重載時,方法調用選擇的是靜態類型,所以此時調用的是Son參數 show((Son) p); } }View Code
輸出的結果是:
2)方法調用中,傳遞的實參的數據類型和方法的形參的數據類型類型不一定要一致,如果實參和形參的數據類型不同,則會優先選擇與實參數據類型匹配度最高的形參數據類型方法進行調用,
需要註意的是:形參和實參的數據類型必須是能夠相互轉換的數據類型,看下面的一個例子
package com.example.demo; public class Demo { /** * * 對show方法進行多種不同參數的重載 */ public static void show(float a) { System.out.println("這是float方法"); }public static void show(Object b) { System.out.println("這是Object方法"); } public static void main(String[] args) { int a=0; String b="b"; show(a); show(b); } }View Code
輸出結果:
3.動態分派調用(方法的重寫)
方法調用選擇的實際類型的方式稱為動態分派。
package com.example.demo; public class Demo { /** * * 創建一個父類和其show方法 * */ static class Parent{ public void show() { System.out.println("這是Parent方法"); } } /** * * 創建一個子類並重寫父類的show方法 * */ static class Child extends Parent{ @Override public void show() { System.out.println("這是Child方法"); } } public static void main(String[] args) { //創建了一個parent實例,這個實例的靜態類型為parent,實際類型為Child Parent parent=new Child(); //方法的重寫選用的是實例的實際類型 parent.show(); } }View Code
輸出結果為:
參考文檔:https://www.cnblogs.com/snailclimb/p/9086337.html
深入了解java虛擬機(JVM) 第十三章 虛擬機字節碼執行引擎