每一個棧幀中存儲着java
1.局部變量表(Local Variables)python
2.操做數棧(Operand Stack)(或表達式棧)編程
3.動態連接(Dynamic Linking)(或執行"運行時常量池"的方法引用)----深刻理解Java多態特性必讀!!數組
4.方法返回地址(Return Adress)(或方法正常退出或者異常退出的定義)緩存
5.一些附加信息安全
其中部分參考書目上,稱方法返回地址、動態連接、附加信息爲幀數據區架構
1.局部變量表也被稱之爲局部變量數組或本地變量表jvm
2.定義爲一個數字數組,主要用於存儲方法參數和定義在方法體內的局部變量這些數據類型包括各種基本數據類型、對象引用(reference),以及returnAddressleixing編程語言
3.因爲局部變量表是創建在線程的棧上,是線程私有的數據,所以不存在數據安全問題函數
4.局部變量表所需的容量大小是在編譯期肯定下來的,並保存在方法的Code屬性的maximum local variables數據項中。在方法運行期間是不會改變局部變量表的大小的
5.方法嵌套調用的次數由棧的大小決定。通常來講,棧越大,方法嵌套調用次數越多。對一個函數而言,他的參數和局部變量越多,使得局部變量表膨脹,它的棧幀就越大,以知足方法調用所需傳遞的信息增大的需求。進而函數調用就會佔用更多的棧空間。
6.局部變量表中的變量只在當前方法調用中有效。在方法執行時,虛擬機經過使用局部變量表完成參數值到參數變量列表的傳遞過程。當方法調用結束後,隨着方法棧幀的銷燬,局部變量表也會隨之銷燬。
利用javap命令對字節碼文件進行解析查看main()方法對應棧幀的局部變量表,如圖:
也能夠在IDEA 上安裝jclasslib byte viewcoder插件查看方法內部字節碼信息剖析,以main()方法爲例
1.參數值的存放老是在局部變量數組的index0開始,到數組長度-1的索引結束
2.局部變量表,最基本的存儲單元是Slot(變量槽)
3.局部變量表中存放編譯期可知的各類基本數據類型(8種),引用類型(reference),returnAddress類型的變量。
4.在局部變量表裏,32位之內的類型只佔用一個slot(包括returnAddress類型),64位的類型(long和double)佔用兩個slot。
byte、short、char、float在存儲前被轉換爲int,boolean也被轉換爲int,0表示false,非0表示true;
long和double則佔據兩個slot。
5.JVM會爲局部變量表中的每個slot都分配一個訪問索引,經過這個索引便可成功訪問到局部變量表中指定的局部變量值
6.當一個實例方法被調用的時候,它的方法參數和方法體內部定義的局部變量將會按照聲明順序被複制到局部變量表中的每個slot上
7.若是須要訪問局部變量表中一個64bit的局部變量值時,只須要使用前一個索引便可。(好比:訪問long或者double類型變量)
8.若是當前幀是由構造方法或者實例方法建立的(意思是當前幀所對應的方法是構造器方法或者是普通的實例方法),那麼該對象引用this將會存放在index爲0的slot處,其他的參數按照參數表順序排列。
9.靜態方法中不能引用this,是由於靜態方法所對應的棧幀當中的局部變量表中不存在this
示例代碼:
public class LocalVariablesTest { private int count = 1; //靜態方法不能使用this public static void testStatic(){ //編譯錯誤,由於this變量不存在與當前方法的局部變量表中!!! System.out.println(this.count); } }
棧幀中的局部變量表中的槽位是能夠重複利用的,若是一個局部變量過了其做用域,那麼在其做用域以後申明的新的局部變量就頗有可能會複用過時局部變量的槽位,從而達到節省資源的目的。
private void test2() { int a = 0; { int b = 0; b = a+1; } //變量c使用以前以及經銷燬的變量b佔據的slot位置 int c = a+1; }
上述代碼對應的棧幀中局部變量表中一共有多少個slot,或者說局部變量表的長度是幾?
答案是3:
變量b的做用域是
{ int b = 0; b = a+1; }
this佔0號、a單獨佔1個槽號、c重複使用了b的槽號
變量的分類:
1.棧 :可使用數組或者鏈表來實現
2.每個獨立的棧幀中除了包含局部變量表之外,還包含一個後進先出的操做數棧,也能夠成爲表達式棧
3.操做數棧,在方法執行過程當中,根據字節碼指令,往棧中寫入數據或提取數據,即入棧(push)或出棧(pop)
某些字節碼指令將值壓入操做數棧,其他的字節碼指令將操做數取出棧,使用他們後再把結果壓入棧。(如字節碼指令bipush操做)
好比:執行復制、交換、求和等操做
代碼舉例
③壓入8;④8出棧,存儲8進入局部變量表;
⑤從局部變量表中把索引爲1和2的是數據取出來,放到操做數棧;⑥iadd相加操做
⑦iadd操做結果23出棧⑧將23存儲在局部變量表索引爲3的位置上istore_3
1.運行時常量池位於方法區(注意: JDK1.7 及以後版本的 JVM 已經將運行時常量池從方法區中移了出來,在 Java 堆(Heap)中開闢了一塊區域存放運行時常量池。)
字節碼中的常量池結構以下:
爲何須要常量池呢?
常量池的做用,就是爲了提供一些符號和常量,便於指令的識別。下面提供一張測試類的運行時字節碼文件格式
2.每個棧幀內部都包含一個指向運行時常量池Constant pool或該棧幀所屬方法的引用。包含這個引用的目的就是爲了支持當前方法的代碼可以實現動態連接。好比invokedynamic指令
3.在Java源文件被編譯成字節碼文件中時,全部的變量和方法引用都做爲符號引用(symbolic Refenrence)保存在class字節碼文件(javap反編譯查看)的常量池裏。好比:描述一個方法調用了另外的其餘方法時,就是經過常量池中指向方法的符號引用來表示的,那麼動態連接的做用就是爲了將這些符號引用(#)最終轉換爲調用方法的直接引用。
在JVM中,將符號引用轉換爲調用方法的直接引用與方法的綁定機制相關
對應的方法的綁定機制爲:早起綁定(Early Binding)和晚期綁定(Late Bingding)。綁定是一個字段、方法或者類在符號引用被替換爲直接引用的過程,這僅僅發生一次。
隨着高級語言的橫空出世,相似於java同樣的基於面向對象的編程語言現在愈來愈多,儘管這類編程語言在語法風格上存在必定的差異,可是它們彼此之間始終保持着一個共性,那就是都支持封裝,集成和多態等面向對象特性,既然這一類的編程語言具有多態特性,那麼天然也就具有早期綁定和晚期綁定兩種綁定方式。
Java中任何一個普通的方法其實都具有虛函數的特徵,它們至關於C++語言中的虛函數(C++中則須要使用關鍵字virtual來顯式定義)。若是在Java程序中不但願某個方法擁有虛函數的特徵時,則可使用關鍵字final來標記這個方法。
子類對象的多態性使用前提:
①類的繼承關係(父類的聲明)②方法的重寫(子類的實現)
實際開發編寫代碼中用的接口,實際執行是導入的的三方jar包已經實現的功能
非虛方法
其餘全部體現多態特性的方法稱爲虛方法
普通調用指令:
1.invokestatic:調用靜態方法,解析階段肯定惟一方法版本;
2.invokespecial:調用<init>方法、私有及父類方法,解析階段肯定惟一方法版本;
3.invokevirtual調用全部虛方法;
4.invokeinterface:調用接口方法;
動態調用指令(Java7新增):
5.invokedynamic:動態解析出須要調用的方法,而後執行 .
前四條指令固化在虛擬機內部,方法的調用執行不可人爲干預,而invokedynamic指令則支持由用戶肯定方法版本。
其中invokestatic指令和invokespecial指令調用的方法稱爲非虛方法
其中invokevirtual(final修飾的除外,JVM會把final方法調用也歸爲invokevirtual指令,但要注意final方法調用不是虛方法)、invokeinterface指令調用的方法稱稱爲虛方法。
/** * 解析調用中非虛方法、虛方法的測試 */ class Father { public Father(){ System.out.println("Father默認構造器"); } public static void showStatic(String s){ System.out.println("Father show static"+s); } public final void showFinal(){ System.out.println("Father show final"); } public void showCommon(){ System.out.println("Father show common"); } } public class Son extends Father{ public Son(){ super(); } public Son(int age){ this(); } public static void main(String[] args) { Son son = new Son(); son.show(); } //不是重寫的父類方法,由於靜態方法不能被重寫 public static void showStatic(String s){ System.out.println("Son show static"+s); } private void showPrivate(String s){ System.out.println("Son show private"+s); } public void show(){ //invokestatic showStatic(" 大頭兒子"); //invokestatic super.showStatic(" 大頭兒子"); //invokespecial showPrivate(" hello!"); //invokespecial super.showCommon(); //invokevirtual 由於此方法聲明有final 不能被子類重寫,因此也認爲該方法是非虛方法 showFinal(); //虛方法以下 //invokevirtual showCommon();//沒有顯式加super,被認爲是虛方法,由於子類可能重寫showCommon info(); MethodInterface in = null; //invokeinterface 不肯定接口實現類是哪個 須要重寫 in.methodA(); } public void info(){ } } interface MethodInterface { void methodA(); }
Java:String info = "硅谷";//靜態語言 JS:var name = "硅谷「;var name = 10;//動態語言 Pythom: info = 130;//更加完全的動態語言
舉個例子:咱們定義三個類、一個Friendly接口
interface Friendly{ void sayHello(); void sayGoodbye(); }
Dog類的虛方法表
class Dog{ public void sayHello(){ } public String toString(){ return "Dog"; } }
可卡犬虛方法表:可卡犬如果使用toString方法無需向上找Object類,只需找到Dog類便可;這是一個效率的提高
class CockerSpaniel extends Dog implements Friendly{ public void sayHello(){ super.sayHello(); } public void sayGoodbye(){ } }
貓類的虛方法表:
class Cat implements Friendly{ public void eat(){ } public void sayHello(){ } public void sayGoodbye(){ } protected void finalize(){ } public String toString(){ } }
1.執行引擎遇到任意一個方法返回的字節碼指令(return),會有返回值傳遞給上層的方法調用者,簡稱正常完成出口;
2.在方法執行的過程當中遇到了異常(Exception),而且這個異常沒有在方法內進行處理,也就是隻要在本方法的異常表中沒有搜素到匹配的異常處理器,就會致使方法退出,簡稱異常完成出口
方法執行過程當中拋出異常時的異常處理,存儲在一個異常處理表,方便在發生異常的時候找處處理異常的代碼。
咱們寫一個demo演示:
字節碼當中的異常處理表:下表的行號不是上圖的代碼的行號,而是其對應字節碼當中的行號
在字節碼當中的4~11行是可能存在異常的代碼,11表明字節碼中可以處理該異常的位置是第11行也就是上圖中的第72行
棧幀中還容許攜帶與java虛擬機實現相關的一些附加信息。例如,對程序調試提供支持的信息。(不少資料都忽略了附加信息)
參考教程:尚硅谷官方--宋紅康
參考書目:深刻理解JVM--周志明