2.構造輸入資料流
FileInputStream fin = new FileInputStream(file);DataInputStream in= new DataInputStream(new BufferedInputStream(fin));
3.讀取位元組碼檔案(.class)的內容
3.1魔法數
//無符號4個位元組 int magic = in.readInt();
3.2次版本號碼
//無符號2個位元組
int minor_version = in.readUnsignedShort();
3.3主要版本號
//無符號2個位元組 int major_version = in.readUnsignedShort();
3.4常量池的個數
//無符號2個位元組
int count = cr.readUnsignedShort();
3.5常量池的內容
//CPInfo是常量池中的常量的類型,不能簡單地把常量池中的類型理解為String類型 CPInfo[] pool = new CPInfo[count]; for (int i = 1; i < count; i++) { // 常量池中常量的類型標記 int tag = cr.readUnsignedByte(); switch (tag) { // 類或介面的符號引用 case CONSTANT_Class: pool[i] = new CONSTANT_Class_info(this, cr); break; // 雙精確度浮點型字面量 case CONSTANT_Double: pool[i] = new CONSTANT_Double_info(cr); i++; break; // 欄位的符號引用 case CONSTANT_Fieldref: pool[i] = new CONSTANT_Fieldref_info(this, cr); break; // 浮點型字面量 case CONSTANT_Float: pool[i] = new CONSTANT_Float_info(cr); break; // 整型字面量 case CONSTANT_Integer: pool[i] = new CONSTANT_Integer_info(cr); break; // 介面方法的引用 case CONSTANT_InterfaceMethodref: pool[i] = new CONSTANT_InterfaceMethodref_info(this, cr); break; // 動態調用指令 case CONSTANT_InvokeDynamic: pool[i] = new CONSTANT_InvokeDynamic_info(this, cr); break; // 長整型字面量 case CONSTANT_Long: pool[i] = new CONSTANT_Long_info(cr); i++; break; // 方法處理器 case CONSTANT_MethodHandle: pool[i] = new CONSTANT_MethodHandle_info(this, cr); break; // 方法類型 case CONSTANT_MethodType: pool[i] = new CONSTANT_MethodType_info(this, cr); break; // 方法引用 case CONSTANT_Methodref: pool[i] = new CONSTANT_Methodref_info(this, cr); break; // 名字和類型 case CONSTANT_NameAndType: pool[i] = new CONSTANT_NameAndType_info(this, cr); break; // 字串 case CONSTANT_String: pool[i] = new CONSTANT_String_info(this, cr); break; // Utf8編碼的字串 case CONSTANT_Utf8: pool[i] = new CONSTANT_Utf8_info(cr); break; // 不合法的類型 default: throw new InvalidEntry(i, tag); } }
構造細節
舉一個例子,比如構造Double類型的常量結構CONSTANT_Double_info,就是在這個結構中儲存一個Double的值。
此外,還維護了一些其它資訊,這個結構的位元組長度、類型標記。
其它一些結構,類似。
3.6訪問標識符
//無符號2個位元組 int access_flag= cr.readUnsignedShort()
3.7這個類在常量池中的下標
//無符號2個位元組 int this_class = in.readUnsignedShort();
3.8這個類的父類在常量池中的下標
//無符號2個位元組 int super_class = in.readUnsignedShort();
3.9父介面的數量
//無符號2個位元組
int int interfaces_count = cr.readUnsignedShort();
3.10父介面的內容
interfaces = new int[interfaces_count]; for (int i = 0; i < interfaces_count; i++){ //父介面的類型,無符號2個位元組 interfaces[i] = cr.readUnsignedShort(); }
3.11欄位的數量
//無符號2個位元組 int int fields_count = cr.readUnsignedShort();
3.12欄位的內容
fields = new Field[fields_count]; for (int i = 0; i < fields_count; i++){ fields[i] = new Field(cr); }//一個欄位Field有訪問標識符、名字、描述符、屬性 access_flags = cr.readUnsignedShort(); name_index = cr.readUnsignedShort(); descriptor = cr.readUnsignedShort() attributes,封裝了欄位的更多詳細資料 3.13方法的數量,無符號2個位元組
int methods_count = cr.readUnsignedShort();
3.14方法的內容
methods = new Method[methods_count]; for (int i = 0; i < methods_count; i++) methods[i] = new Method(cr); // 一個方法Method有訪問標識符、名字、描述符、屬性 access_flags = cr.readUnsignedShort(); name_index = cr.readUnsignedShort(); descriptor = cr.readUnsignedShort() attributes,封裝了方法的更多詳細資料
3.15屬性的數量
//無符號2個位元組 int attrs_count = cr.readUnsignedShort();
3.16屬性的內容
Attribute[] attrs = new Attribute[attrs_count]; for (int i = 0; i < attrs_count; i++) { attrs[i] = readAttribute(); }/** * 讀取一個屬性Attribute */ public Attribute readAttribute() throws IOException { //名字的索引 int name_index = readUnsignedShort(); //屬性內容的長度 int length = readInt(); byte[] data = new byte[length]; //讀取length個長度的位元組資訊到data中 readFully(data); DataInputStream prev = in; in = new DataInputStream(new ByteArrayInputStream(data)); try { //根據名字的索引、位元組數組形式的內容構造一個Attribute對象 return attributeFactory.createAttribute(this, name_index, data); } finally { in = prev; } }
4.總結
一個位元組碼檔案(.class)幾乎含有一個類或介面的所有資訊。
這些資訊雖然以二進位形式存在,但是它們的儲存結構仍然是有規律的。
按照儲存格式來依次讀取相應的位元組數目,就可以完整地解析一個位元組碼檔案。
5.挑戰極限
講完了位元組碼檔案的的結構和讀取位元組碼檔案的方法,有讀者可能會問,位元組碼檔案是怎麼產生的。
我們寫的Java程式都是文字格式設定的.java檔案,它們是如何轉換的呢。
其實,.java到.class的轉換過程,就是Java編譯器javac的主要功能。
Java編譯器javac比較有難度,剛剛開始研究,如有心得體會和研究成功,一定第一時間分享出來。
6.相關閱讀 OpenJDK源碼研究筆記(一)-參數檢查&拋出帶嚴重錯誤提示資訊的異常 OpenJDK源碼研究筆記(二)-Comparable和Comparator2個介面的作用和區別(一道經典的Java筆試面試題) OpenJDK源碼研究筆記(三)-RandomAccess等標記介面的作用
OpenJDK源碼研究筆記(四)-編寫和組織可複用的工具類和方法
OpenJDK源碼研究筆記(五)-緩衝Integer等類型的頻繁使用的資料和對象,大幅度提升效能(一道經典的Java筆試題)
OpenJDK源碼研究筆記(六)--觀察者模式工具類(Observer和Observable)和應用樣本
OpenJDK源碼研究筆記(七)–Java位元組碼檔案(.class)的結構
原文參見:http://FansUnion.cn/articles/2974