最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

解析Java Class 文件過程

 更新時間:2019年05月31日 09:52:40   作者:HalfStackDeveloper  
class文件全名稱為Java class文件,主要在平臺無關性和網(wǎng)絡移動性方面使Java更適合網(wǎng)絡。它在平臺無關性方面的任務是:為Java程序提供獨立于底層主機平臺的二進制形式的服務。下面我們來詳細解讀下它吧

前言:

身為一個java程序員,怎么能不了解JVM呢,倘若想學習JVM,那就又必須要了解Class文件,Class之于虛擬機,就如魚之于水,虛擬機因為Class而有了生命?!渡钊肜斫鈐ava虛擬機》中花了一整個章節(jié)來講解Class文件,可是看完后,一直都還是迷迷糊糊,似懂非懂。正好前段時間看見一本書很不錯:《自己動手寫Java虛擬機》,作者利用go語言實現(xiàn)了一個簡單的JVM,雖然沒有完整實現(xiàn)JVM的所有功能,但是對于一些對JVM稍感興趣的人來說,可讀性還是很高的。作者講解的很詳細,每個過程都分為了一章,其中一部分就是講解如何解析Class文件。

這本書不太厚,很快就讀完了,讀完后,收獲頗豐。但是紙上得來終覺淺,絕知此事要躬行,我便嘗試著自己解析Class文件。go語言雖然很優(yōu)秀,但是終究不熟練,尤其是不太習慣其把類型放在變量之后的語法,還是老老實實用java吧。

話不多說,先貼出項目地址:https://github.com/HalfStackDeveloper/ClassReader

Class文件

什么是Class文件?

java之所以能夠實現(xiàn)跨平臺,便在于其編譯階段不是將代碼直接編譯為平臺相關的機器語言,而是先編譯成二進制形式的java字節(jié)碼,放在Class文件之中,虛擬機再加載Class文件,解析出程序運行所需的內容。每個類都會被編譯成一個單獨的class文件,內部類也會作為一個獨立的類,生成自己的class。

基本結構

隨便找到一個class文件,用Sublime Text打開是這樣的:

是不是一臉懵逼,不過java虛擬機規(guī)范中給出了class文件的基本格式,只要按照這個格式去解析就可以了:

ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}

ClassFile中的字段類型有u1、u2、u4,這是什么類型呢?其實很簡單,就是分別表示1個字節(jié),2個字節(jié)和4個字節(jié)。

開頭四個字節(jié)為:Magic,是用來唯一標識文件格式的,一般被稱作magic number(魔數(shù)),這樣虛擬機才能識別出所加載的文件是否是class格式,class文件的魔數(shù)為cafebabe。不只是class文件,基本上大部分文件都有魔數(shù),用來標識自己的格式。

接下來的部分主要是class文件的一些信息,如常量池、類訪問標志、父類、接口信息、字段、方法等,具體的信息可參考《Java虛擬機規(guī)范》。

解析

字段類型

上面說到ClassFile中的字段類型有u1、u2、u4,分別表示1個字節(jié),2個字節(jié)和4個字節(jié)的無符號整數(shù)。java中short、int、long分別為2、4、8個字節(jié)的有符號整數(shù),去掉符號位,剛好可以用來表示u1、u2、u4。

public class U1 {
public static short read(InputStream inputStream) {
byte[] bytes = new byte[1];
try {
inputStream.read(bytes);
} catch (IOException e) {
e.printStackTrace();
}
short value = (short) (bytes[0] & 0xFF);
return value;
}
}
public class U2 {
public static int read(InputStream inputStream) {
byte[] bytes = new byte[2];
try {
inputStream.read(bytes);
} catch (IOException e) {
e.printStackTrace();
}
int num = 0;
for (int i= 0; i < bytes.length; i++) {
num <<= 8;
num |= (bytes[i] & 0xff);
}
return num;
}
}
public class U4 {
public static long read(InputStream inputStream) {
byte[] bytes = new byte[4];
try {
inputStream.read(bytes);
} catch (IOException e) {
e.printStackTrace();
}
long num = 0;
for (int i= 0; i < bytes.length; i++) {
num <<= 8;
num |= (bytes[i] & 0xff);
}
return num;
}
}


常量池

定義好字段類型后,我們就可以讀取class文件了,首先是讀取魔數(shù)之類的基本信息,這部分很簡單:

FileInputStream inputStream = new FileInputStream(file);
ClassFile classFile = new ClassFile();
classFile.magic = U4.read(inputStream);
classFile.minorVersion = U2.read(inputStream);
classFile.majorVersion = U2.read(inputStream);

這部分只是熱熱身,接下來的大頭在于常量池。解析常量池之前,我們先來解釋一下常量池是什么。

常量池,顧名思義,存放常量的資源池,這里的常量指的是字面量和符號引用。字面量指的是一些字符串資源,而符號引用分為三類:類符號引用、方法符號引用和字段符號引用。通過將資源放在常量池中,其他項就可以直接定義成常量池中的索引了,避免了空間的浪費,不只是class文件,Android可執(zhí)行文件dex也是同樣如此,將字符串資源等放在DexData中,其他項通過索引定位資源。java虛擬機規(guī)范給出了常量池中每一項的格式:

cp_info {
u1 tag;
u1 info[]; 
}

上面的這個格式只是一個通用格式,常量池中真正包含的數(shù)據(jù)有14種格式,每種格式的tag值不同,具體如下所示:

由于格式太多,文章中只挑選一部分講解:

這里首先讀取常量池的大小,初始化常量池:

//解析常量池
int constant_pool_count = U2.read(inputStream);
ConstantPool constantPool = new ConstantPool(constant_pool_count);
constantPool.read(inputStream);

接下來再逐個讀取每項內容,并存儲到數(shù)組cpInfo中,這里需要注意的是,cpInfo[]下標從1開始,0無效,且真正的常量池大小為constant_pool_count-1。

public class ConstantPool {
public int constant_pool_count;
public ConstantInfo[] cpInfo;

public ConstantPool(int count) {
constant_pool_count = count;
cpInfo = new ConstantInfo[constant_pool_count];
}

public void read(InputStream inputStream) {
for (int i = 1; i < constant_pool_count; i++) {
short tag = U1.read(inputStream);
ConstantInfo constantInfo = ConstantInfo.getConstantInfo(tag);
constantInfo.read(inputStream);
cpInfo[i] = constantInfo;
if (tag == ConstantInfo.CONSTANT_Double || tag == ConstantInfo.CONSTANT_Long) {
i++;
}
}
}
}


我們先來看看CONSTANT_Utf8格式,這一項里面存放的是MUTF-8編碼的字符串:

CONSTANT_Utf8_info { 
u1 tag;
u2 length;
u1 bytes[length]; 
}


那么如何讀取這一項呢?

public class ConstantUtf8 extends ConstantInfo {
public String value;
@Override
public void read(InputStream inputStream) {
int length = U2.read(inputStream);
byte[] bytes = new byte[length];
try {
inputStream.read(bytes);
} catch (IOException e) {
e.printStackTrace();
}
try {
value = readUtf8(bytes);
} catch (UTFDataFormatException e) {
e.printStackTrace();
}
}
private String readUtf8(byte[] bytearr) throws UTFDataFormatException {
//copy from java.io.DataInputStream.readUTF()
}
}


很簡單,首先讀取這一項的字節(jié)數(shù)組長度,接著調用readUtf8(),將字節(jié)數(shù)組轉化為String字符串。

再來看看CONSTANT_Class這一項,這一項存儲的是類或者接口的符號引用:

CONSTANT_Class_info {
u1 tag;
u2 name_index;
}

注意這里的name_index并不是直接的字符串,而是指向常量池中cpInfo數(shù)組的name_index項,且cpInfo[name_index]一定是CONSTANT_Utf8格式。

public class ConstantClass extends ConstantInfo {
public int nameIndex;
@Override
public void read(InputStream inputStream) {
nameIndex = U2.read(inputStream);
}
}

常量池解析完畢后,就可以供后面的數(shù)據(jù)使用了,比方說ClassFile中的this_class指向的就是常量池中格式為CONSTANT_Class的某一項,那么我們就可以讀取出類名:

int classIndex = U2.read(inputStream);
ConstantClass clazz = (ConstantClass) constantPool.cpInfo[classIndex];
ConstantUtf8 className = (ConstantUtf8) constantPool.cpInfo[clazz.nameIndex];
classFile.className = className.value;
System.out.print("classname:" + classFile.className + "\n");


字節(jié)碼指令

解析常量池之后還需要接著解析一些類信息,如父類、接口類、字段等,但是相信大家最好奇的還是java指令的存儲,大家都知道,我們平時寫的java代碼會被編譯成java字節(jié)碼,那么這些字節(jié)碼到底存儲在哪呢?別急,講解指令之前,我們先來了解下ClassFile中的method_info,其格式如下:

method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}


method_info里主要是一些方法信息:如訪問標志、方法名索引、方法描述符索引及屬性數(shù)組。這里要強調的是屬性數(shù)組,因為字節(jié)碼指令就存儲在這個屬性數(shù)組里。屬性有很多種,比如說異常表就是一個屬性,而存儲字節(jié)碼指令的屬性為CODE屬性,看這名字也知道是用來存儲代碼的了。屬性的通用格式為:

attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}


根據(jù)attribute_name_index可以從常量池中拿到屬性名,再根據(jù)屬性名就可以判斷屬性種類了。

Code屬性的具體格式為:

Code_attribute {
u2 attribute_name_index; u4 attribute_length;
u2 max_stack;
u2 max_locals;
u4 code_length;
u1 code[code_length];
u2 exception_table_length; 
{
u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];
u2 attributes_count;
attribute_info attributes[attributes_count];
}


其中code數(shù)組里存儲就是字節(jié)碼指令,那么如何解析呢?每條指令在code[]中都是一個字節(jié),我們平時javap命令反編譯看到的指令其實是助記符,只是方便閱讀字節(jié)碼使用的,jvm有一張字節(jié)碼與助記符的對照表,根據(jù)對照表,就可以將指令翻譯為可讀的助記符了。這里我也是在網(wǎng)上隨便找了一個對照表,保存到本地txt文件中,并在使用時解析成HashMap。代碼很簡單,就不貼了,可以參考我代碼中InstructionTable.java。

接下來我們就可以解析字節(jié)碼了:

for (int j = 0; j < methodInfo.attributesCount; j++) {
if (methodInfo.attributes[j] instanceof CodeAttribute) {
CodeAttribute codeAttribute = (CodeAttribute) methodInfo.attributes[j];
for (int m = 0; m < codeAttribute.codeLength; m++) {
short code = codeAttribute.code[m];
System.out.print(InstructionTable.getInstruction(code) + "\n");
}
}
}


運行

整個項目終于寫完了,接下來就來看看效果如何,隨便找一個class文件解析運行:

哈哈,是不是很贊!

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

相關文章

  • Java 流的高級使用之收集數(shù)據(jù)解析

    Java 流的高級使用之收集數(shù)據(jù)解析

    這篇文章主要介紹了Java 流的高級使用之收集數(shù)據(jù)解析,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • 淺談Java響應式系統(tǒng)

    淺談Java響應式系統(tǒng)

    第一次聽到reactive這個詞還是在幾年前,偶然了解到了Rxjava這個項目,仿佛為我打開了一扇新的大門,Rxjava是ReactiveX的java實現(xiàn),ReactiveX家族除了Rxjava還有RxJS, Rx.NET,RxScala等等。
    2021-06-06
  • Java判斷字符串是否為IP地址的方法

    Java判斷字符串是否為IP地址的方法

    這篇文章主要為大家詳細介紹了Java判斷字符串是否為IP地址的方法,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-08-08
  • Java編程實現(xiàn)beta分布的采樣或抽樣實例代碼

    Java編程實現(xiàn)beta分布的采樣或抽樣實例代碼

    這篇文章主要介紹了Java編程實現(xiàn)beta分布的采樣或抽樣實例,分享了相關代碼示例,小編覺得還是挺不錯的,具有一定借鑒價值,需要的朋友可以參考下
    2018-01-01
  • Java并發(fā)編程之顯示鎖ReentrantLock和ReadWriteLock讀寫鎖

    Java并發(fā)編程之顯示鎖ReentrantLock和ReadWriteLock讀寫鎖

    這篇文章主要介紹了Java并發(fā)編程之顯示鎖ReentrantLock和ReadWriteLock讀寫鎖,本文講解了ReentrantLock概況、Lock接口、Lock使用、輪詢鎖的和定時鎖、公平性、可中斷獲鎖獲取操作等內容,需要的朋友可以參考下
    2015-04-04
  • Spring中任務調度之解讀@Scheduled和@Schedules注解的使用

    Spring中任務調度之解讀@Scheduled和@Schedules注解的使用

    這篇文章主要介紹了Spring中任務調度之解讀@Scheduled和@Schedules注解的使用,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-04-04
  • Win11系統(tǒng)下載安裝java的詳細過程

    Win11系統(tǒng)下載安裝java的詳細過程

    這篇文章主要介紹了Win11如何下載安裝java,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-05-05
  • JDBC獲取數(shù)據(jù)庫連接的5種方式實例

    JDBC獲取數(shù)據(jù)庫連接的5種方式實例

    JDBC是一種用于執(zhí)行SQL語句的JavaAPI,為多種關系數(shù)據(jù)庫提供統(tǒng)一訪問,它由一組用Java語言編寫的類和接口組成,提供了諸如查詢和更新數(shù)據(jù)庫中數(shù)據(jù)的方法,這篇文章主要給大家介紹了關于JDBC獲取數(shù)據(jù)庫連接的5種方式,需要的朋友可以參考下
    2022-06-06
  • MybatisPlus中插入數(shù)據(jù)后獲取該對象主鍵值的實現(xiàn)

    MybatisPlus中插入數(shù)據(jù)后獲取該對象主鍵值的實現(xiàn)

    這篇文章主要介紹了MybatisPlus中插入數(shù)據(jù)后獲取該對象主鍵值,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-09-09
  • Flowable中定時器的玩法詳解

    Flowable中定時器的玩法詳解

    這篇文章主要為大家詳細介紹了Flowable中定時器的各種玩法,文中的示例代碼講解詳細,具有一定的借鑒價值,感興趣的小伙伴可以跟隨小編一起了解一下
    2022-11-11

最新評論

永安市| 札达县| 周宁县| 邹城市| 新乡市| 泰和县| 甘泉县| 连城县| 衡东县| 留坝县| 津南区| 邵东县| 三穗县| 汽车| 芦溪县| 平原县| 昌都县| 调兵山市| 石渠县| 凭祥市| 南郑县| 嘉黎县| 陕西省| 民乐县| 汾西县| 昭苏县| 海城市| 玉屏| 正定县| 梁河县| 荆州市| 浦城县| 龙游县| 神农架林区| 张家港市| 德化县| 历史| 洪雅县| 罗江县| 济南市| 来安县|