本是新年,怎奈新冠肆掠,路上行人,男女老幼幾乎是全副口罩,形色匆匆;偶爾有一兩個裸露口鼻的,估計都是沒囤到口罩的,這幾天藥店幾乎都是貼上大字:口罩沒貨。看着網絡上病毒消息滿天飛,我也響應在家作貢獻的號召。上班時,都是早出晚歸,幾乎只有早上能看到娃,出門時,娃每次都說:see you tomorrow 。遇上疫情,每天在家帶娃,終於能夠多多陪伴了;別說,帶娃還真比上班費神。想着小時候,特別想有一個玩具小船,動手給娃作了一個,附圖一張。把娃帶好了,也得思考下學習的事兒。學習java有段時間了,想起以前學習java時,看着Class<?> 這樣的符號就怵,不明白其表示的含義,又重讀《java編程思想》第14章, 趁着這樣的時間好好整理了一下,直面當時的怵。java
Class<?> - 類的類型,是運行時類型信息,也就是 RTTI - RTTI - RunTime Type Infomation;所謂一切皆對象,類也是一個對象,而類的類型信息,就叫作Class對象。RTTI使得咱們能夠在運行時發現和使用類型信息。之前以爲RTTI離我很遠(java菜鳥),其實多態機制正是由於類對象攜帶了類的類型信息,在類型轉化時能夠識別到對象的類型。舉個栗子,以下, ChildClassTest向上轉型爲 SuperClassTest時,丟失了子類類型信息,而運行時,向下轉型時,又使用RTTI 獲取了實際類型,從而能夠正常打印出 ChildClassTest。可是,爲何向上轉型丟失類型信息,再向下轉型時,能夠獲取到實際的類型,這要從RTTI 的工做原理提及了。程序員
public class SuperClassTest { } public class ChildClassTest extends SuperClassTest { }
SuperClassTest superClassTest = new ChildClassTest();
PrintTool.print(superClassTest);
#打印
com.hj.tool.klass.ChildClassTest@685f4c2e
前面的例子中,這種在運行時,肯定類的實際類型是虛擬機的動態分派機制。 爲啥對象能夠找到類型信息呢,由於普通對象是被Class對象建立的,而Class對象包含了類的有關信息。下圖爲Class對象的加載過程,當咱們在建立普通對象時,會先判斷此類的Class對象是否加載(每一個類都有一個Class對象),若是已經加載,就使用Class對象生成普通對象;若是未加載,就須要經過字節碼建立Class對象,再生成普通對象。在虛擬機層面,則是運行時,把變量 new ChildClassTest()的引用存放於 LocalVariableTable 的 slot中,執行print時(其實就是執行toString()方法),實際是執行invokevirtual 指令,找到方法的實際接收者,再執行toString()。而 invokevirtual 解析的過程,根據《深刻理解java虛擬機》中的描述過程以下:編程
1)找到操做數棧頂的第一個元素所指向的對象的實際類型,記做C。 2)若是在類型C中找到與常量中的描述符和簡單名稱都相符的方法,則進行訪問權限校驗,若是經過則返回這個方法的直接引用,查找過程結束;若是不經過,則返回java.lang.IllegalAccessError異常。 3)不然,按照繼承關係從下往上依次對C的各個父類進行第2步的搜索和驗證過程。 4)若是始終沒有找到合適的方法,則拋出java.lang.AbstractMethodError異常。 因爲invokevirtual指令執行的第一步就是在運行期肯定接收者的實際類型,因此兩次調用中的invokevirtual指令把常量池中的類方法符號引用解析到了不一樣的直接引用上,
這個過程就是Java語言中方法重寫的本質。咱們把這種在運行期根據實際類型肯定方法執行版本的分派過程稱爲動態分派。
既然Class對象來源於字節碼,那就來分析下.class文件的內容,引用《java虛擬機規範》中關於classFile的格式以下:「每一個class文件都由字節流組成,每一個字節含有8個二進制位。全部16位,32位,64位長度的數據將經過構形成2個,4個,8個連續的8位字節來表示。」規範中定義了每一個項的字節長度,以及結構,分析的過程仍是挺有意思的:原來咱們寫的代碼都被編譯成那樣的格式。說來也慚愧,java用了這麼久,連一個簡單的.class文件都沒有分析過。數組
每一個class文件都對應以下結構(JDK 8,不一樣版本結構不是徹底同樣),其中包括兩類數據類型:u(1/2/4), _info; u 後面的數字表示n個字節,而 每一個_info 又有特定的格式。 具體能夠參看《java虛擬機規範 se 8》第4章內容。網絡
咱們來看下具體的一個類,post
package com.hj.tool.klass; /** * @Description TODO * @Author jijunjian * @Date 2020-01-27 20:47 * @Version 1.0 */ public class ByteCodeTest { private int m ; public int inc(){ return m+1; } }
使用xxd ByteCodeTest.class 查看編譯後的.class文件(16進制),獲得以下內容。乍一看,是否是徹底看不到,咱們的類是如何組織的哇。等咱們按class文件的格式整理後,狀況就徹底不同了。學習
cafe babe 0000 0034 0016 0a00 0400 1209 0003 0013 0700 1407 0015 0100 016d 0100 0149 0100 063c 696e 6974 3e01 0003 2829 5601 0004 436f 6465 0100 0f4c 696e 654e 756d 6265 7254 6162 6c65 0100 124c 6f63 616c 5661 7269 6162 6c65 5461 626c 6501 0004 7468 6973 0100 204c 636f 6d2f 686a 2f74 6f6f 6c2f 6b6c 6173 732f 4279 7465 436f 6465 5465 7374 3b01 0003 696e 6301 0003 2829 4901 000a 536f 7572 6365 4669 6c65 0100 1142 7974 6543 6f64 6554 6573 742e 6a61 7661 0c00 0700 080c 0005 0006 0100 1e63 6f6d 2f68 6a2f 746f 6f6c 2f6b 6c61 7373 2f42 7974 6543 6f64 6554 6573 7401 0010 6a61 7661 2f6c 616e 672f 4f62 6a65 6374 0021 0003 0004 0000 0001 0002 0005 0006 0000 0002 0001 0007 0008 0001 0009 0000 002f 0001 0001 0000 0005 2ab7 0001 b100 0000 0200 0a00 0000 0600 0100 0000 0900 0b00 0000 0c00 0100 0000 0500 0c00 0d00 0000 0100 0e00 0f00 0100 0900 0000 3100 0200 0100 0000 072a b400 0204 60ac 0000 0002 000a 0000 0006 0001 0000 000e 000b 0000 000c 0001 0000 0007 000c 000d 0000 0001 0010 0000 0002 0011
如下是整理後的結果,這個過程仍是須要些耐心的。可是這個時間花得決絕物超所值。我解析了大部份內容,基本都註釋了,其中常量池佔了不少內容,但實際上是最簡單部分,method中關於code屬性是比較麻煩的。不一樣版本編譯獲得的內容可能會有不一樣。this
#魔數 cafe babe #版本 jdk 8 0000 0034 # 常量池有21 個,第一個,是保留 0016 # 第一個常量 CONSTANT_Methodref_info{ u1 tag //10 u2 class_index //指向CONSTANT_Class_info;表示類 u2 name_and_type_index //指向CONSTANT_NameAndType,表示方法名、方法描述符 } 0a tag 10 0004 class_index 指向 4 0012 name_and_type_index 指向 18 # 第二個常量 tag=9 CONSTANT_Fieldref_info{ u1 tag //9 u2 class_index //指向CONSTANT_Class_info;既能夠表示類、也能夠表示接口 u2 name_and_type_index //指向CONSTANT_NameAndType,表示字段名、字段描述符 } 09 tag 9 0003 class_index 指向 3 0013 name_and_type_index 指向19 # 第三個常量 tag=7 CONSTANT_Class_info{ u1 tag //tag=7 u2 name_index // name_index是索引值,指向CONSTANT_Utf8_info } 07 tag 7 0014 name_index 指向 20 com/hj/tool/klass/ByteCodeTest # 第4個常量 tag=7 07 0015 name_index 指向 21 # 第5個常量 tag=01 CONSTANT_Utf8_info{ u1 tag //1 u2 length u1 bytes[length] //長度爲length的字符串數組 } 01 tag 0001 length 6d asc 109=m # 第6個常量 tag=01 01 0001 length 49 asc 73 I 表示int # 第7個常量 tag=01 01 0006 3c 69 6e 69 74 3e <init> # 第8個常量 tag=01 utf8 字符串數組 01 0003 28 29 56 ()V # 第9個常量 tag=01 utf8 字符串數組 01 0004 43 6f 64 65 Code # 第10個常量 tag=01 utf8 字符串數組 01 000f length=15 4c 69 6e 65 Line 4e 75 6d 62 65 72 number 54 61 62 6c 65 Table # 第11個常量 tag=01 utf8 字符串數組 01 0012 4c 6f 63 LocalVariableTable 61 6c 56 61 72 69 61 62 6c 65 54 61 62 6c 65 # 第12個常量 tag=01 utf8 字符串數組 01 0004 74 68 69 73 this # 第13個常量 tag=01 utf8 字符串數組 01 0020 4c 63 6f 6d 2f 68 6a 2f 74 6f 6f 6c 2f 6b 6c 61 73 73 2f 42 79 74 65 43 6f 64 65 54 65 73 74 3b Lcom/hj/tool/klass/ByteCodeTest; 3b=; # 第14個常量 tag=01 utf8 字符串數組 01 0003 69 6e 63 inc # 第15個常量 tag=01 utf8 字符串數組 01 0003 28 29 49 ()I # 第16個常量 tag=01 utf8 字符串數組 01 000a 53 6f 75 72 63 65 46 69 6c 65 SourceFile # 第17個常量 tag=01 utf8 字符串數組 01 0011 17個 42 79 74 65 43 6f 64 65 54 65 73 74 2e 6a 61 76 61 ByteCodeTest.java # 第18個常量 tag=12 NameAndType CONSTANT_NameAndType{ u1 tag //12 u2 name_index //指向CONSTANT_Utf8_info,表示名稱 u2 descriptor_index //指向CONSTANT_Utf8_info,表示描述符 } 0c tag 12 nameAndType 0007 name_index 指向第7個常量 <init> 0008 descriptor_index 指向第8個常量 ()V # 第19個常量 tag=12 NameAndType 0c 0005 m 0006 I # 第20個常量 tag=01 utf8 字符串數組 01 001e 63 6f 6d 2f 68 6a 2f 74 6f 6f 6c 2f 6b 6c 61 73 73 2f 42 79 74 65 43 6f 64 65 54 65 73 74 com/hj/tool/klass/ByteCodeTest # 第21個常量 tag=01 utf8 字符串數組 01 0010 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 java/lang/Object access_flags 0021 表示是public ,是1.2之後因此21 類索引,父類索引,接口索引 0003 類索引 2字節 指向第三個常量 class-info 又指向 和指向第20個 com/hj/tool/klass/ByteCodeTest 0004 父類索引 2字節 同理指向 java/lang/Object 0000 接口索引 無 0001 field_count u2 1個 field_info[1] field_info{ u2 access_flags //表示字段的訪問權限、屬性 u2 name_index //對常量池的索引 u2 descriptor_index //對常量池的索引 u2 attributes_count //附加屬性的數量 attribute_info attributes[attributes_count] //每一個成員是attribute_info結構 } 0002 private 0005 name_index m 0006 descriptor_index I 0000 attributes_count 0 0002 method_count method_info{ u2 access_flags //表示方法的訪問權限、屬性 u2 name_index //對常量池的索引 u2 descriptor_index //對常量池的索引 u2 attributes_count//附加屬性的數量 attribute_info attributes[attributes_count] //每一個成員是attribute_info結構 } # 第一個 method init 0001 access_flags public 0007 name_index <init> 0008 descriptor_index ()V 0001 attributes_count 1 attribute_info{ u2 attribute_name_index //常量池索引 u4 attribute_length u1 info[attribute_length] } 0009 attribute_name_index Code 0000 002f attribute_length 47 0001 max_stack 0001 max_locals 0000 0005 code_attribute_length 2a b7 0001 b100 00 00 02 00 0a 00 00 00 06 00 01 00 00 00 09 00 0b 00 00 00 0c 00 01 00 00 00 05 00 0c 00 0d 00 00 # 第二個method 0001 access_flags public 000e name_index 14 inc 000f descriptor_index 15 ()I 0001 attributes_count 1 attribute_info 0009 attribute_name_index Code 0000 0031 attribute_length 49 00 02 max_stack 00 01 max_locals 一個 00 00 00 07 code_length 7 2a aload_0 將第一個引用類型的本地變量 b4 getfield 獲取指定類型的實例字段 m #下面這兩個指令沒弄明白是啥意思, 00 nop 不作 02 iconst_ml 將-1 推到棧頂 04 iconst_1 將1 推到棧頂 60 iadd 將棧頂兩個相加,結果壓入棧頂 ac ireturn 返回int 00 00 exception_table_length 00 02 attritutes_count 2 00 0a LineNumberTable 00 00 00 06 length=6 00 01 00 00 00 0e 00 0b LocalVariableTable 00 00 00 0c length =12 00 01 00 00 00 07 00 0c 00 0d 00 00 0001 attributes_count 1 0010 attribute_name_index 16 SourceFile 0000 0002 attribute_length 2 0011 sourcefile_index 17 指向常量池中 ByteCodeTest.java
文章寫到這裏,感受很是艱難,一是感受寫得不知所云,估計只有本身能明白,二是感受本身的理解還很淺顯。沒動手以前,感受啥都理解了,真正開始動手吧,又感受啥都沒理解。這即是從輸入到輸出的真實過程;讀只是輸入,沒法造成真正的理解,只有持續輸出才能真正領悟,而這個輸出的過程纔是消化的過程。寫得過程當中,又不斷翻閱資料,把原來點點的理解,鏈接成斷斷續續的線,但願之後能夠再深刻學習,把這些點點的東西,連成線,匯成面。spa
成爲一名優秀的程序員!3d
文章參考了不少《jjava編程思想》,《java虛擬機規範 se 8》,《深刻理解java虛擬機》第二版中的內容。