[目錄]
概述
1 概述
- 不同的虛擬機(jī)實現(xiàn)里面,執(zhí)行引擎在執(zhí)行Java代碼的時候可能會有解釋執(zhí)行(
通過解釋器執(zhí)行
)和編譯執(zhí)行(通過即時編譯器產(chǎn)生本地代碼執(zhí)行
)兩種選擇也可能兩者兼?zhèn)?/li> - 從外觀上看起來,所有的Java虛擬機(jī)的執(zhí)行引擎都是一致的:輸入的是
字節(jié)碼文件
,處理過程是字節(jié)碼解析的等效過程
,輸出的是執(zhí)行結(jié)果
- 本章將主要從概念模型的角度來講解虛擬機(jī)的方法調(diào)用和字節(jié)碼執(zhí)行。
2 運(yùn)行時棧幀結(jié)構(gòu)
棧幀(Stack Frame)是虛擬機(jī)運(yùn)行時數(shù)據(jù)區(qū)中的
虛擬機(jī)棧(Virtual Machine Stack)的棧元素
,用于支持虛擬機(jī)進(jìn)行方法調(diào)用和方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu)存儲了方法的
局部變量表
、操作數(shù)棧
、動態(tài)連接
和方法返回地址
等信息每一個方法從調(diào)用開始至執(zhí)行完成的過程
**---------> **一個棧幀在虛擬機(jī)棧里面從入棧到出棧的過程
在編譯程序代碼的時候,棧幀中需要多大的局部變量表,多深的操作數(shù)棧都已經(jīng)完全確定了,并且寫入到方法表的Code屬性之中
一個線程中的方法調(diào)用鏈可能會很長,很多方法都同時處于執(zhí)行狀態(tài)(方法間互相調(diào)用)
對于執(zhí)行引擎來說,在活動線程中,只有位于棧頂的棧幀才是有效的,稱為
當(dāng)前棧幀(Current StackFrame)
,與這個棧幀相關(guān)聯(lián)的方法稱為當(dāng)前方法(Current Method)
。
2.1 局部變量表
- 局部變量表(Local Variable Table)是一組變量值存儲空間,用于存放
方法參數(shù)和方法內(nèi)部定義的局部變量
。在Java程序編譯為Class文件時,就在方法的Code屬性
的max_locals
數(shù)據(jù)項中確定了該方法所需要分配的局部變量表的最大容量。 - 局部變量表的容量以
變量槽(Variable Slot,下稱Slot)
為最小單位 - 一個Slot可以存放一個32位以內(nèi)的數(shù)據(jù)類型,Java中占用32位以內(nèi)的數(shù)據(jù)類型有
boolean
、byte
、char
、short
、int
、float
、reference
和returnAddress
種類型 -
reference類型
表示對一個對象實例的引用,一是從此引用中直接或間接地查找到對象在Java堆中的數(shù)據(jù)存放的起始地址索引
,二是此引用中直接或間接地查找到對象所屬數(shù)據(jù)類型在方法區(qū)中的存儲的類型信息
2.2 操作數(shù)棧
-
操作數(shù)棧(Operand Stack)
也常稱為操作棧,它是一個后入先出(Last In First Out,LIFO)棧。同局部變量表一樣,操作數(shù)棧的最大深度
也在編譯的時候?qū)懭氲?code>Code屬性的max_stacks數(shù)據(jù)項
中。操作數(shù)棧的每一個元素可以是任意的Java數(shù)據(jù)類型,包括long和double。32位數(shù)據(jù)類型所占的棧容量為1,64位數(shù)據(jù)類型所占的棧容量為2。在方法執(zhí)行的任何時候,操作數(shù)棧的深度都不會超過在max_stacks數(shù)據(jù)項中設(shè)定的最大值。
2.3 動態(tài)連接
- 每個棧幀都包含一個指向運(yùn)行時常量池中該棧幀所屬方法的引用,持有這個引用是為了支持方法調(diào)用過程中的
動態(tài)連接(Dynamic Linking)
。
2.4 方法返回地址
兩種返回的方法:
正常完成出口(Normal Method Invocation Completion)
和異常完成出口(Abrupt Method Invocation Completion)
無論采用何種退出方式,在方法退出之后,都需要返回到方法被調(diào)用的位置,程序才能繼續(xù)執(zhí)行,方法返回時可能需要在棧幀中保存一些信息,用來幫助恢復(fù)它的上層方法的執(zhí)行狀態(tài)。一般來說,方法正常退出時,調(diào)用者的PC計數(shù)器的值可以作為返回地址,棧幀中很可能會保存這個計數(shù)器值。而方法異常退出時,返回地址是要通過異常處理器表來確定的,棧幀中一般不會保存這部分信息
3 方法調(diào)用
方法調(diào)用并不等同于方法執(zhí)行,方法調(diào)用階段唯一的任務(wù)就是確定被調(diào)用方法的版本(即調(diào)用哪一個方法),暫時還不涉及方法內(nèi)部的具體運(yùn)行過程。
3.1 解析
- 調(diào)用目標(biāo)在程序代碼寫好、編譯器進(jìn)行編譯時就必須確定下來。這類方法的調(diào)用稱為
解析(Resolution)
-
第七章講過:目標(biāo)方法在Class文件里面都是一個常量池中的符號引用,在類加載的解析階段,會將其中的一部分符號引用轉(zhuǎn)化為直接引用,這
種解析能成立的前提是:方法在程序真正運(yùn)行之前就有一個可確定的調(diào)用版本,并且這個方法的調(diào)用版本在運(yùn)行期是不可改變的 - 符合“編譯期可知,運(yùn)行期不可變”這個要求的方法,主要包括
靜態(tài)方法
和私有方法
兩大類
Java虛擬機(jī)里面提供了5條方法調(diào)用字節(jié)碼指令
方法名 | 解釋 | 備注 |
---|---|---|
invokestatic | 調(diào)用靜態(tài)方法 | 無 |
invokespecial | 調(diào)用實例構(gòu)造器<init>方法、私有方法和父類方法 | 無 |
invokevirtual | 調(diào)用所有的虛方法 | 無 |
invokeinterface | 調(diào)用接口方法,會在運(yùn)行時再確定一個實現(xiàn)此接口的對象 | 無 |
invokedynamic | todo | todo |
只要能被
invokestatic
和invokespecial
指令調(diào)用的方法,都可以在解析階段中確定唯一的調(diào)用版本,符合這個條件的有靜態(tài)方法
、私有方法
、實例構(gòu)造器
、父類方法
4類,它們在類加載的時候就會把符號引用解析為該方法的直接引用
被
invokestatic
和invokespecial
指令調(diào)用的方法 稱為非虛方法
,與之相
反,其他方法稱為虛方法
(除去final方法,后文會提到)
3.2 分派
3.2.1 靜態(tài)分派
- 定義: 依賴靜態(tài)類型來定位方法執(zhí)行版本的分派動作
- 靜態(tài)分派發(fā)生在
編譯階段
- 確定靜態(tài)分派的動作實際上不是由虛擬機(jī)來執(zhí)行的。
示例代碼
package study8;
/**
* Created by haicheng.lhc on 05/04/2017.
*
* @author haicheng.lhc
* @date 2017/04/05
*/
public class StaticDispatch {
static abstract class Human {
}
static class Man extends Human {
}
static class Woman extends Human {
}
public void sayHello(Human guy) {
System.out.println("hello,guy!");
}
public void sayHello(Man guy) {
System.out.println("hello,gentleman!");
}
public void sayHello(Woman guy) {
System.out.println("hello,lady!");
}
public static void main(String[] args) {
Human man = new Man();
Human woman = new Woman();
StaticDispatch sr = new StaticDispatch();
sr.sayHello(man);
sr.sayHello(woman);
}
}
結(jié)果分析:
我們把上面代碼中的
“Human”
稱為變量的靜態(tài)類型(Static Type)
,或者叫做的外觀類型(Apparent Type),后面的“Man”
則稱為變量的實際類型(Actual Type)
,靜態(tài)類型和實際類型在程序中都可以發(fā)生一些變化,區(qū)別是靜態(tài)類型的變化僅僅在使用時發(fā)生,變量本身的靜態(tài)類型不會被改變,并且最終的靜態(tài)類型是在編譯期可知的;而實際類型變化的結(jié)果在運(yùn)行期才可確定,編譯器在編譯程序的時候并不知道一個對象的實際類型是什么.虛擬機(jī)(準(zhǔn)確地說是編譯器)在重載時是通過參數(shù)的靜態(tài)類型而不是實際類型作為判定依據(jù)的
3.2.2.動態(tài)分派
- (Override)有著很密切的關(guān)聯(lián)
示例代碼
package study8;
/**
* Created by haicheng.lhc on 05/04/2017.
*
* @author haicheng.lhc
* @date 2017/04/05
*/
public class DynamicDispatch {
static abstract class Human {
protected abstract void sayHello();
}
static class Man extends Human {
@Override
protected void sayHello() {
System.out.println("man say hello");
}
}
static class Woman extends Human {
@Override
protected void sayHello() {
System.out.println("woman say hello");
}
}
public static void main(String[] args) {
Human man = new Man();
Human woman = new Woman();
man.sayHello();
woman.sayHello();
man = new Woman();
man.sayHello();
}
}
解析
使用javap分析
Compiled from "DynamicDispatch.java"
public class study8.DynamicDispatch extends java.lang.Object
SourceFile: "DynamicDispatch.java"
InnerClass:
#9= #4 of #7; //Woman=class study8/DynamicDispatch$Woman of class study8/DynamicDispatch
#11= #2 of #7; //Man=class study8/DynamicDispatch$Man of class study8/DynamicDispatch
abstract #13= #12 of #7; //Human=class study8/DynamicDispatch$Human of class study8/DynamicDispatch
minor version: 0
major version: 50
Constant pool:
const #1 = Method #8.#22; // java/lang/Object."<init>":()V
const #2 = class #23; // study8/DynamicDispatch$Man
const #3 = Method #2.#22; // study8/DynamicDispatch$Man."<init>":()V
const #4 = class #24; // study8/DynamicDispatch$Woman
const #5 = Method #4.#22; // study8/DynamicDispatch$Woman."<init>":()V
const #6 = Method #12.#25; // study8/DynamicDispatch$Human.sayHello:()V
const #7 = class #26; // study8/DynamicDispatch
const #8 = class #27; // java/lang/Object
const #9 = Asciz Woman;
const #10 = Asciz InnerClasses;
const #11 = Asciz Man;
const #12 = class #28; // study8/DynamicDispatch$Human
const #13 = Asciz Human;
const #14 = Asciz <init>;
const #15 = Asciz ()V;
const #16 = Asciz Code;
const #17 = Asciz LineNumberTable;
const #18 = Asciz main;
const #19 = Asciz ([Ljava/lang/String;)V;
const #20 = Asciz SourceFile;
const #21 = Asciz DynamicDispatch.java;
const #22 = NameAndType #14:#15;// "<init>":()V
const #23 = Asciz study8/DynamicDispatch$Man;
const #24 = Asciz study8/DynamicDispatch$Woman;
const #25 = NameAndType #29:#15;// sayHello:()V
const #26 = Asciz study8/DynamicDispatch;
const #27 = Asciz java/lang/Object;
const #28 = Asciz study8/DynamicDispatch$Human;
const #29 = Asciz sayHello;
{
public study8.DynamicDispatch();
Code:
Stack=1, Locals=1, Args_size=1
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 9: 0
line 22: 4
public static void main(java.lang.String[]);
Code:
Stack=2, Locals=3, Args_size=1
0: new #2; //class study8/DynamicDispatch$Man
3: dup
4: invokespecial #3; //Method study8/DynamicDispatch$Man."<init>":()V
7: astore_1
8: new #4; //class study8/DynamicDispatch$Woman
11: dup
12: invokespecial #5; //Method study8/DynamicDispatch$Woman."<init>":()V
15: astore_2
16: aload_1
17: invokevirtual #6; //Method study8/DynamicDispatch$Human.sayHello:()V
20: aload_2
21: invokevirtual #6; //Method study8/DynamicDispatch$Human.sayHello:()V
24: new #4; //class study8/DynamicDispatch$Woman
27: dup
28: invokespecial #5; //Method study8/DynamicDispatch$Woman."<init>":()V
31: astore_1
32: aload_1
33: invokevirtual #6; //Method study8/DynamicDispatch$Human.sayHello:()V
36: return
LineNumberTable:
line 30: 0
line 31: 8
line 32: 16
line 33: 20
line 34: 24
line 35: 32
line 36: 36
}
雖然17 21 語句完全一樣,但是結(jié)果卻不一樣,原因是:從invokevirtual指令的多態(tài)查找過程開始說起,invokevirtual指令的運(yùn)行時解析過程大致分為以下幾個步驟:
1)找到操作數(shù)棧頂?shù)牡谝粋€元素所指向的對象的實際類型,記作C。
2)如果在類型C中找到與常量中的描述符和簡單名稱都相符的方法,則進(jìn)行訪問權(quán)限校驗,如果通過則返回這個方法的直接引用,查找過程結(jié)束;如果不通過,則返回java.lang.IllegalAccessError異常。
3)否則,按照繼承關(guān)系從下往上依次對C的各個父類進(jìn)行第2步的搜索和驗證過程。
4)如果始終沒有找到合適的方法,則拋出java.lang.AbstractMethodError異常。
3.2.3單分派與多分派
- 方法的接收者與方法的參數(shù)統(tǒng)稱為方法的宗量,這個定義最早應(yīng)該來源于《Java與模式》一書。根據(jù)分派基于多少種宗量,可以將分派劃分為單分派和多分派兩種
- 以Java語言的靜態(tài)分派屬于多分派類型
- Java語言的動態(tài)分派屬于單分派類型
3.2.4 虛擬機(jī)動態(tài)分派的實現(xiàn)
-
虛方法表(Vritual Method Table,也稱為vtable,與此對應(yīng)的,在invokeinterface執(zhí)行時也會用到接口方法表——Inteface Method Table,簡稱itable)
,使用虛方法表索引來代替元數(shù)據(jù)查找以提高性能。
3.3 動態(tài)類型語言支持
4 基于棧的字節(jié)碼解釋執(zhí)行引擎
- 虛擬機(jī)是如何調(diào)用方法的內(nèi)容已經(jīng)講解完畢,從本節(jié)開始,我們來探討虛擬機(jī)是如何執(zhí)行方法中的字節(jié)碼指令的
- 整個運(yùn)算過程的中間變量都以操作數(shù)棧的出棧、入棧為信息交換途徑