日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Jvm之用java解析class文件

發布時間:2025/3/21 编程问答 52 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Jvm之用java解析class文件 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言:

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

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

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

Class文件

什么是Class文件?

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

基本結構

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


屏幕快照 2017-02-06 上午8.44.42.png

是不是一臉懵逼,不過java虛擬機規范中給出了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個字節,2個字節和4個字節。

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

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

解析

字段類型

上面說到ClassFile中的字段類型有u1、u2、u4,分別表示1個字節,2個字節和4個字節的無符號整數。java中short、int、long分別為2、4、8個字節的有符號整數,去掉符號位,剛好可以用來表示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文件了,首先是讀取魔數之類的基本信息,這部分很簡單:

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可執行文件dex也是同樣如此,將字符串資源等放在DexData中,其他項通過索引定位資源。java虛擬機規范給出了常量池中每一項的格式:

cp_info {u1 tag;u1 info[]; }

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


屏幕快照 2017-02-06 下午2.20.08.png

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

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

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

接下來再逐個讀取每項內容,并存儲到數組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;@Overridepublic 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()} }

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

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

CONSTANT_Class_info {u1 tag;u2 name_index; }

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

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

常量池解析完畢后,就可以供后面的數據使用了,比方說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");

字節碼指令

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

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

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

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

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

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

接下來我們就可以解析字節碼了:

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文件解析運行:


屏幕快照 2017-02-06 下午3.55.10.png

哈哈,是不是很贊!

最后再貼一下項目地址:https://github.com/HalfStackDeveloper/ClassReader,歡迎Fork And Star!

總結

Class文件看起來很復雜,其實真正解析起來,也沒有那么難,關鍵是要自己動手試試,才能徹底理解,希望各位看完后也能覺知此事要躬行!

參考:

1. 周志明《java虛擬機規范(JavaSE7)》

2. 張秀宏《自己動手寫Java虛擬機》

3. 周志明《深入理解Java虛擬機(第2版)》

(如有錯誤,歡迎指正!)

(轉載請標明ID:半棧工程師,個人博客:https://halfstackdeveloper.github.io)

歡迎關注我的知乎專欄:https://zhuanlan.zhihu.com/halfstack

歡迎Follow我的github:?https://github.com/HalfStackDeveloper

總結

以上是生活随笔為你收集整理的Jvm之用java解析class文件的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 色男人天堂av | 免费av一区二区三区 | 名校风暴在线观看免费高清完整 | 免费国产黄色 | 久色视频在线观看 | 一区二区片| 亚洲加勒比 | 天天鲁一鲁摸一摸爽一爽 | 精品国产一区二区三区四区精华 | 久久重口味 | 97色伦图片 | 久久久久无码国产精品不卡 | www.国产欧美 | 在线国产一区二区 | 91全免费| 小sao货cao死你 | 熟妇人妻系列aⅴ无码专区友真希 | 国产精品免费入口 | 性――交――性――乱a | 蜜臀av无码精品人妻色欲 | 日韩中文字幕网站 | 麻豆蜜桃在线观看 | 少妇激情一区二区三区视频 | 欧美在线色 | wwwxxxx在线观看| 视频在线不卡 | 韩国av在线 | 中文字幕人妻一区二 | 亚洲国产精品成人va在线观看 | av毛片网站 | 亚洲小说专区 | 美日韩在线视频 | 日本黄色生活片 | 韩国主播青草55部完整 | 男人深夜网站 | 国产卡一卡二 | 亚洲欧洲精品成人久久奇米网 | 秋霞影院一区二区 | 国产社区在线 | 黄网站色视频免费观看 | 人人爽久久涩噜噜噜网站 | 免费网站在线观看黄色 | 在线不卡日本 | 伊人久久久久久久久久久久久 | 99自拍 | 特大黑人巨交吊性xxxx视频 | 色婷婷综合五月 | 亚洲精品国产精品乱码在线观看 | 国产第九页 | 孕期1ⅴ1高h | 国产成人精品久久二区二区 | 久久久久久免费观看 | 在线看的av网站 | 久草视频福利 | 成年人午夜免费视频 | 欧美日韩一区二区三区不卡 | 欧美婷婷六月丁香综合色 | 懂色一区二区三区免费观看 | 精品777| 在线成人中文字幕 | 欧美一区日韩一区 | 国产深夜视频 | 超碰在线91 | 在线成人国产 | 青青草原亚洲视频 | 综合色99| 亚洲丁香 | 婷婷色在线| 成人日皮视频 | 日本高清www | 免费国产一区 | 亚洲一个色 | 少妇献身老头系列 | 欧美另类老妇 | 超碰66| 最新中文字幕视频 | 欧美日韩免费一区二区 | 99热最新在线 | 中文字幕第五页 | 欧美精品日韩在线观看 | 天天色棕合合合合合合合 | 黄色片hd | 亚洲天堂成人在线 | 久久久久久网 | 国产美女av在线 | 二十四小时在线更新观看 | 亚洲同性gay激情无套 | 日本精品一二三 | 久草视频在线免费播放 | 黄色一级片免费 | 久久97人妻无码一区二区三区 | 性欧美69| av加勒比| 成人做爰免费视频免费看 | 日日爱99 | 日本久久久久久久久 | aaaa黄色| 中文字幕一区二区免费 | 久久婷婷色综合 |