Java Class 位元組碼檔案結構詳解
Class位元組碼中有兩種資料型別:
- 位元組資料直接量:這是基本的資料型別。共細分為u1、u2、u4、u8四種,分別代表連續的1個位元組、2個位元組、4個位元組、8個位元組組成的整體資料。
- 表:表是由多個基本資料或其他表,按照既定順序組成的大的資料集合。表是有結構的,它的結構體現在,組成表的成分所在的位置和順序都是已經嚴格定義好的。
Class位元組碼總體結構如下:
具體詳解請參考http://www.blogjava.net/DLevin/archive/2011/09/05/358033.html
我在這裡要說明幾個細節問題:
- 為什麼說常量表的數量是constant_pool_count-1,且索引從1開始而不是0。其實根本原因在於,索引為0也是一個常量(保留常量),只不過它不存在常量表,這個常量就對應null值。因此加上這個系統保留常量,常量個數共為constant_pool_count個,但是常量表數量要減1。
- 在常量池中,如果存在long型或double型字面量,它們會佔用兩個連續索引。比如:假設一個類中只有一個int型字面量1和一個double型字面量1(當然這種假設是不可能的,因為總會有類名字面量等),則常量池個數為3,而不是2。這正是因為double字面量佔用了兩個連續的索引。
接下來,貼出一個小demo來展示如何讀取位元組碼:
ClassParser負責把握Class位元組碼整體結構的解析。
package com.lixin; import java.io.IOException; import java.io.InputStream; public class ClassParser { private InputStream in; public ClassParser(InputStream in) { this.in = in; } public void parse() throws IOException { // 魔數 magicNumber(); // 主次版本號 version(); // 常量池 constantPool(); // 類或介面修飾符 accessFlag(); // 繼承關係(當前類、父類、父介面) inheritence(); // 欄位集合 fieldList(); // 方法集合 methodList(); // 屬性集合 attributeList(); } private void attributeList() throws IOException { line(); int attrLength = StreamUtils.read2(in); System.out.println("共有"+attrLength+"個屬性"); for (int i=0;i<attrLength;i++) { line(); attribute(); } } private void attribute() throws IOException { int nameIndex = StreamUtils.read2(in); int length = StreamUtils.read4(in); byte[] info = StreamUtils.read(in, length); System.out.println("nameIndex:"+nameIndex); System.out.println("length:"+length); System.out.println("info:"+info); } private void methodList() throws IOException { int length = StreamUtils.read2(in); System.out.println("共有"+length+"個方法"); for (int i=0;i<length;i++) method(); } private void method() throws IOException { System.out.println("---------------------"); int accessFlag = StreamUtils.read2(in); int nameIndex = StreamUtils.read2(in); int descriptorIndex = StreamUtils.read2(in); System.out.println("accessFlag:"+accessFlag); System.out.println("nameIndex:"+nameIndex); System.out.println("descriptorIndex:"+descriptorIndex); attributeList(); } private void fieldList() throws IOException { line(); int length = StreamUtils.read2(in); System.out.println("共有"+length+"個欄位"); for (int i=0;i<length;i++) { System.out.println("-----------------------------"); int accessFlag = StreamUtils.read2(in); int nameIndex = StreamUtils.read2(in); int descriptorIndex = StreamUtils.read2(in); System.out.println("accessFlag:"+accessFlag); System.out.println("nameIndex:"+nameIndex); System.out.println("descriptorIndex:"+descriptorIndex); attributeList(); } } private void inheritence() throws IOException { line(); int thisClassRef = StreamUtils.read2(in); int superClassRef = StreamUtils.read2(in); System.out.println("thisClassRef:"+thisClassRef); System.out.println("superClassRef:"+superClassRef); int interfaceLen = StreamUtils.read2(in); System.out.println("介面數量:"+interfaceLen); for (int i=0;i<interfaceLen;i++) { int interfaceRef = StreamUtils.read2(in); System.out.println("interfaceRef:"+interfaceRef); } } private void accessFlag() throws IOException { line(); int accessFlag = StreamUtils.read2(in); System.out.println("accessFlag:0x"+Integer.toHexString(accessFlag)+"("+accessFlag+")"); } private void constantPool() throws IOException { new ConstantPoolParser(in).constPool(); } private void version() throws IOException { line(); int minorVersion = StreamUtils.read2(in); int majorVersion = StreamUtils.read2(in); System.out.println("版本:"+majorVersion+"."+minorVersion); } private void magicNumber() throws IOException { line(); int magic = StreamUtils.read4(in); System.out.println("魔數:"+Integer.toHexString(magic).toUpperCase()); } private void line() { System.out.println("----------------------"); } }
ConstPoolParser負責常量池的解析(因為常量池表較多,且資料量也較大,因此單獨拉出來解析)
package com.lixin; import java.io.IOException; import java.io.InputStream; public class ConstPoolParser { public static final int Utf8_info = 1; public static final int Integer_info = 3; public static final int Float_info = 4; public static final int Long_info = 5; public static final int Double_info = 6; public static final int Class_info = 7; public static final int String_info = 8; public static final int Fieldref_info = 9; public static final int Methodref_info = 10; public static final int InterfaceMethodref_info = 11; public static final int NameAndType_info = 12; public static final int MethodHandle_info = 15; public static final int MethodType_info = 16; public static final int InvokeDynamic_info = 18; private InputStream in; public ConstPoolParser(InputStream in) { this.in = in; } public void constPool() throws IOException { line(); int length = StreamUtils.read2(in); System.out.println("共有"+length+"個常量"); boolean doubleBytes = false; for (int i = 1; i < length; i++) { if (doubleBytes) { doubleBytes = false; continue; } line(); System.out.println("常量索引:"+i); int flag = StreamUtils.read1(in); // System.out.println("標誌:"+flag); switch (flag) { case Utf8_info: utf8Info(); continue; case Integer_info: integerInfo(); continue; case Float_info: floatInfo(); continue; case Long_info: doubleBytes = true; longInfo(); continue; case Double_info: doubleBytes = true; doubleInfo(); continue; case Class_info: classInfo(); continue; case String_info: stringInfo(); continue; case Fieldref_info: fieldrefInfo(); continue; case Methodref_info: methodrefInfo(); continue; case InterfaceMethodref_info: interfaceMethodrefInfo(); continue; case NameAndType_info: nameAndTypeInfo(); continue; case MethodHandle_info: methodHandleInfo(); continue; case MethodType_info: methodTypeInfo(); continue; case InvokeDynamic_info: invokeDynamicInfo(); continue; default: System.err.println(flag); throw new RuntimeException("unknown"); } } } private void line() { System.out.println("----------------------"); } private void utf8Info() throws IOException { int length = StreamUtils.read2(in); byte[] buf = StreamUtils.read(in, length); String s = new String(buf,0,buf.length); System.out.println("utf8Info表:"); System.out.println("值:"+s); } private void integerInfo() throws IOException { System.out.println("integerInfo表:"); int value = StreamUtils.read4(in); System.out.println("值:"+value); } private void floatInfo() throws IOException { System.out.println("floatInfo表:"); int value = StreamUtils.read4(in); float f = Float.intBitsToFloat(value); System.out.println("值:"+f); } private void longInfo() throws IOException { System.out.println("longInfo表:"); long value = StreamUtils.read8(in); System.out.println("值:"+value); } private void doubleInfo() throws IOException { System.out.println("doubleInfo表:"); long value = StreamUtils.read8(in); double d = Double.longBitsToDouble(value); System.out.println("值:"+d); } private void classInfo() throws IOException { System.out.println("classInfo表:"); int index = StreamUtils.read2(in); System.out.println("index:" + index); } private void stringInfo() throws IOException { System.out.println("stringInfo表:"); int index = StreamUtils.read2(in); System.out.println("index:" + index); } private void fieldrefInfo() throws IOException { int classIndex = StreamUtils.read2(in); int nameAndTypeIndex = StreamUtils.read2(in); System.out.println("fieldrefInfo表:"); System.out.println("classIndex:" + classIndex); System.out.println("nameAndTypeIndex:" + nameAndTypeIndex); } private void methodrefInfo() throws IOException { int classIndex = StreamUtils.read2(in); int nameAndTypeIndex = StreamUtils.read2(in); System.out.println("methodrefInfo表:"); System.out.println("classIndex:" + classIndex); System.out.println("nameAndTypeIndex:" + nameAndTypeIndex); } private void interfaceMethodrefInfo() throws IOException { int classIndex = StreamUtils.read2(in); int nameAndTypeIndex = StreamUtils.read2(in); System.out.println("interfaceMethodrefInfo表:"); System.out.println("classIndex:" + classIndex); System.out.println("nameAndTypeIndex:" + nameAndTypeIndex); } private void nameAndTypeInfo() throws IOException { int nameIndex = StreamUtils.read2(in); int typeIndex = StreamUtils.read2(in); System.out.println("nameAndTypeInfo表:"); System.out.println("nameIndex:" + nameIndex); System.out.println("typeIndex:" + typeIndex); } private void methodHandleInfo() throws IOException { int referenceKind = StreamUtils.read1(in); int referenceIndex = StreamUtils.read2(in); System.out.println("methodHandleInfo表:"); System.out.println("referenceKind:"+referenceKind); System.out.println("referenceIndex:"+referenceIndex); } private void methodTypeInfo() throws IOException { System.out.println("methodTypeInfo表:"); int descriptorIndex = StreamUtils.read2(in); System.out.println("descriptorIndex:"+descriptorIndex); } private void invokeDynamicInfo() throws IOException { int bootstrapMethodAttrIndex = StreamUtils.read2(in); int nameAndTypeIndex = StreamUtils.read2(in); System.out.println("bootstrapMethodAttrIndex:"+bootstrapMethodAttrIndex); System.out.println("nameAndTypeIndex:"+nameAndTypeIndex); } }
StreamUtils負責從輸入位元組流中讀取資料
package com.lixin; import java.io.IOException; import java.io.InputStream; public class StreamUtils { public static int read1(InputStream in) throws IOException { return in.read() & 0xff; } public static int read2(InputStream in) throws IOException{ return (read1(in) << 8) | read1(in); } public static int read4(InputStream in) throws IOException { return (read2(in) <<16) | read2(in); } public static long read8(InputStream in) throws IOException { long high = read4(in) & 0xffffffffl; long low = read4(in) & 0xffffffffl; return (high << 32) | (low); } public static byte[] read(InputStream in,int length) throws IOException { byte[] buf = new byte[length]; in.read(buf, 0, length); return buf; } }
TestClass為待解析的目標類,讀者可以任意改寫此類來多做實驗
package com.lixin; public class TestClass { private int a = 5; protected char c = 'c'; double x = 1.1; long y = 111; public void show() { } }
測試方法入口:
package com.lixin; import java.io.InputStream; /** * 程式入口 * @author lixin * */ public class App { public static void main(String[] args) throws Exception { InputStream in = Class.class.getResourceAsStream("/com/lixin/TestClass.class"); ClassParser parser = new ClassParser(in); parser.parse(); } }
最後,我們可以使用jdk中的javap進行位元組碼反編譯,來對比我們的讀取與反編譯結果差別,用於查錯。
javap -v TestClass.class >./out.txt
相關文章
- Class檔案結構&位元組碼指令
- 位元組碼檔案結構詳解
- Java 虛擬機器之五:Java位元組碼檔案結構Java虛擬機
- java class檔案詳解Java
- Java Class檔案詳解Java
- JAVA Class類檔案結構Java
- 位元組碼檔案的內部結構之謎
- 《Java虛擬機器原理圖解》 1.1、class檔案基本組織結構Java虛擬機圖解
- 位元組碼詳解
- 位元組碼檔案解剖
- java 位元組流檔案複製方法總結Java
- 例項分析JAVA CLASS的檔案結構Java
- Java Class檔案結構例項分析(下)Java
- Java Class檔案結構例項分析(上)Java
- Class類檔案結構
- Class 檔案格式詳解
- C/C++ 結構體位元組對齊詳解C++結構體
- 類檔案結構_class類檔案的的結構
- MachO 檔案結構詳解Mac
- Java虛擬機器之Class類檔案結構Java虛擬機
- Java 位元組碼Java
- 如果你還不瞭解 Java Class 檔案結構,來看看這篇吧Java
- 深入解析Class類檔案的結構
- JVM學習--Class類檔案結構JVM
- class檔案的基本結構及proxy原始碼分析二原始碼
- Java位元組碼指令Java
- JVMClass詳解之二Method位元組碼指令JVM
- 【深入Java虛擬機器】之二:Class類檔案結構Java虛擬機
- Java Class類詳解Java
- Nuxt.js 深入淺出:目錄結構與檔案組織詳解UXJS
- ☕[Java技術指南](1)Class類檔案的結構介紹(上篇)Java
- java class檔案解析Java
- JAVA動態位元組碼Java
- 【Java】JVM位元組碼分析JavaJVM
- 詳解Android Gradle生成位元組碼流程AndroidGradle
- 什麼是位元組碼?python位元組碼詳細介紹!Python
- C/C++位元組詳解C++
- 如何實現一個Java Class位元組解析器Java