您好,登錄后才能下訂單哦!
本篇內(nèi)容主要講解“JVM中的Class文件結(jié)構(gòu)”,感興趣的朋友不妨來看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“JVM中的Class文件結(jié)構(gòu)”吧!
本文主要介紹了Class
文件的主要組成,包括魔數(shù)、版本號(hào)、常量池、訪問標(biāo)志等。
Class
文件概覽根據(jù)JVM
規(guī)范,一個(gè)Class
文件可以非常嚴(yán)謹(jǐn)?shù)孛枋鰹椋?/p>
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]; }
下面會(huì)按順序詳細(xì)介紹里面的各個(gè)字段。
魔數(shù)(Magic Number
)作為Class
的標(biāo)志,用來告訴JVM
這是一個(gè)Class
文件,魔數(shù)是一個(gè)4字節(jié)的無符號(hào)整數(shù),固定為0xCAFEBABE
。如果一個(gè)Class
文件不以0xCAFEBABE
開頭,那么會(huì)拋出如下錯(cuò)誤:
Linux
下可以直接使用vim
打開class
文件進(jìn)行查看,比如需要打開一個(gè)Test.class
文件,可以輸入如下命令:
vim -b Test.class :%!xxd
切換到十六進(jìn)制后就可以看到魔數(shù)了:
魔數(shù)后面緊跟著Class
的小版本和大版本號(hào),這表示當(dāng)前Class
文件是由哪個(gè)版本的編譯期產(chǎn)生的。小版本和大版本后都是占用兩個(gè)字節(jié),比如下圖:
0000
是小版本號(hào)
0037
是大版本號(hào),十進(jìn)制為55
,也就是對(duì)應(yīng)JDK 11
版本的編譯期
在版本號(hào)后面,緊跟著就是常量池的數(shù)量以及若干個(gè)常量池表項(xiàng):
其中每一個(gè)常量池表項(xiàng)都具有標(biāo)簽屬性:
對(duì)應(yīng)關(guān)系舉例如下:
tag
為3:類型為CONSTANT_Integer
tag
為4:類型為CONSTANT_Float
等等,比如CONSTANT_Integer
結(jié)構(gòu)如下:
CONSTANT_Integer_info { u1 tag; u4 bytes; }
一個(gè)tag
加上一個(gè)四字節(jié)的無符號(hào)整數(shù)。其他類型大部分類似,篇幅限制,詳細(xì)請(qǐng)看JVM規(guī)范。
訪問標(biāo)記使用兩個(gè)字節(jié)表示,用于表明該類的訪問信息,比如public
/abstract
等,對(duì)應(yīng)關(guān)系如下:
ACC_PUBLIC
:0x0001
,表示public
類
ACC_FINAL
:0x0010
,表示是否為final
類
ACC_SUPER
:0x0020
,表示使用增強(qiáng)的方法調(diào)用父類的方法
ACC_INTERFACE
:0x0200
,表示是否為接口
ACC_ABSTRACT
:0x0400
,表示是否為抽象類
ACC_SYNTHETIC
:0x1000
,由編譯期產(chǎn)生的類,沒有源碼對(duì)應(yīng)
ACC_ANNOTATION
:0x2000
,表示是否是注釋
ACC_ENUM
:0x4000
,表示是否為枚舉
格式如下:
u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count];
其中this_class
與super_class
都是兩個(gè)字節(jié)的無符號(hào)整數(shù),指向常量池中的一個(gè)CONSTANT_Class
,表示當(dāng)前的類型以及父類。另外,由于一個(gè)類可以實(shí)現(xiàn)多個(gè)接口,因此需要以數(shù)組形式保存多個(gè)接口的索引,如果沒有實(shí)現(xiàn)任何接口,則interfaces_count
為0。
字段的格式如下:
u2 fields_count; field_info fields[fields_count];
fields_count
是一個(gè)2字節(jié)的無符號(hào)整數(shù),字段數(shù)量之后是具體的字段信息,每個(gè)字段都是一個(gè)field_info
的結(jié)構(gòu),如下所示:
field_info { u2 access_flags; //訪問標(biāo)記,類似于類的訪問標(biāo)記,可以表示public/private/static等等 u2 name_index; //兩字節(jié)整數(shù),指向常量池中的CONSTANT_Utf8 u2 descriptor_index; //也是兩字節(jié)整數(shù),用于描述字段類型,也指向常量池中的CONSTANT_Utf8 u2 attributes_count; //屬性數(shù)量 attribute_info attributes[attributes_count]; //屬性,比如存儲(chǔ)初始化值,一些注釋信息等,需要使用attribute_info } attribute_info { u2 attribute_name_index; //屬性名字,指向常量池的索引 u4 attribute_length; //屬性長(zhǎng)度 u1 info[attribute_length]; //字節(jié)數(shù)組表示的信息 }
方法的格式如下:
u2 methods_count; method_info methods[methods_count];
其中每一個(gè)method_info
結(jié)構(gòu)表示一個(gè)方法:
method_info { u2 access_flags; //訪問標(biāo)記,標(biāo)記方法為public/private等等 u2 name_index; //方法名稱,一個(gè)指向常量池的索引 u2 descriptor_index; //方法描述符,也是一個(gè)指向常量符的索引 u2 attributes_count; //屬性數(shù)量 attribute_info attributes[attributes_count]; //屬性,和字段類似,方法也可以攜帶屬性,一個(gè)屬性數(shù)量+一個(gè)屬性描述數(shù)組 }
Code
屬性方法的主要內(nèi)容存放在屬性中,在屬性里面最重要的一個(gè)屬性就是Code
,Code
存放著方法的字節(jié)碼等信息,結(jié)構(gòu)如下:
Code_attribute { u2 attribute_name_index; //屬性名稱,指向常量池的索引 u4 attribute_length; //屬性長(zhǎng)度,不包括前6字節(jié)(u2+u4) u2 max_stack; //操作數(shù)棧最大深度 u2 max_locals; //局部變量表的最大值 u4 code_length; //字節(jié)碼長(zhǎng)度 u1 code[code_length]; //字節(jié)碼內(nèi)容本身 u2 exception_table_length; //異常處理表長(zhǎng)度 { u2 start_pc; //四個(gè)字段表示在start_pc到end_pc兩個(gè)偏移量之間 u2 end_pc; //如果遇到了catch_type指向的異常 u2 handler_pc; //代碼就跳轉(zhuǎn)到handler_pc位置執(zhí)行 u2 catch_type; } exception_table[exception_table_length]; //異常表 u2 attributes_count; attribute_info attributes[attributes_count]; }
Code
屬性本身也包含其他屬性以進(jìn)一步存儲(chǔ)一些額外信息,主要包括:
LineNumberTable
LocalVariableTable
StackMapTable
LineNumberTable
LineNumberTable
用于記錄字節(jié)碼偏移量和行號(hào)的對(duì)應(yīng)關(guān)系,結(jié)構(gòu)如下:
LineNumberTable_attribute { u2 attribute_name_index; //指向常量池的索引 u4 attribute_length; //屬性長(zhǎng)度 u2 line_number_table_length; //表項(xiàng)記錄條數(shù) { u2 start_pc; //字節(jié)碼偏移量 u2 line_number; //字節(jié)碼偏移量對(duì)應(yīng)的行號(hào) } line_number_table[line_number_table_length]; //表數(shù)組,每一個(gè)元素對(duì)應(yīng)的是一個(gè)<start_pc,line_number>元組 }
LocalVariableTable
這個(gè)屬性也叫局部變量表,記錄了一個(gè)方法中所有的局部變量,結(jié)構(gòu)如下:
LocalVariableTable_attribute { u2 attribute_name_index; //當(dāng)前屬性名字,指向常量池的索引 u4 attribute_length; //屬性長(zhǎng)度 u2 local_variable_table_length; //局部變量表的表項(xiàng)條目 { u2 start_pc; //當(dāng)前局部變量開始位置 u2 length; //當(dāng)前局部變量長(zhǎng)度(可用于計(jì)算結(jié)束位置) u2 name_index; //局部變量名稱,指向常量池的索引 u2 descriptor_index; //局部變量的類型描述,指向常量池的索引 u2 index; //局部變量在當(dāng)前棧幀的局部變量表中的槽位 } local_variable_table[local_variable_table_length]; }
StackMapTable
StackMapTable
中含有若干個(gè)棧映射幀(Stack Map Frame
)的數(shù)據(jù),不包含運(yùn)行時(shí)所需要的信息,僅用作Class
文件的類型校驗(yàn),結(jié)構(gòu)如下:
StackMapTable_attribute { u2 attribute_name_index; //常量池索引,恒為"StackMapTable" u4 attribute_length; //屬性長(zhǎng)度 u2 number_of_entries; //棧映射幀的數(shù)量 stack_map_frame entries[number_of_entries]; //具體的棧映射幀 } union stack_map_frame { //每個(gè)棧映射幀被定義為一個(gè)枚舉值,取值如下 same_frame; //具體每個(gè)取值的意義可以查看JVM規(guī)范 same_locals_1_stack_item_frame; //https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.7.4 same_locals_1_stack_item_frame_extended; chop_frame; same_frame_extended; append_frame; full_frame; }
每個(gè)棧映射幀是為了說明在一個(gè)特定的字節(jié)碼偏移位置上,系統(tǒng)的數(shù)據(jù)類型是什么,包括局部變量表的類型和操作數(shù)棧的類型。
ASM
簡(jiǎn)單使用ASM
是一個(gè)Java
字節(jié)碼操作庫,很多著名的庫都依賴于該庫,比如AspectJ
、CGLIB
等等。但是ASM
的性能遠(yuǎn)遠(yuǎn)超過CGLIB
等高層字節(jié)碼庫,因?yàn)?code>ASM更加接近底層,使用更為靈活且功能更為強(qiáng)大。
下面是一個(gè)簡(jiǎn)單的使用ASM
輸出Hello World
的例子:
package com.company; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; public class Main extends ClassLoader implements Opcodes { public static void main(String[] args) throws Exception{ //創(chuàng)建ClassWriter,指定COMPUTE_MAXS和COMPUTE_FRAMES,分別表示計(jì)算最大局部變量表以及最深操作數(shù)棧 ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); //通過ClassWriter設(shè)置類的基本信息,比如public訪問標(biāo)記,類名為Example cw.visit(V11,ACC_PUBLIC,"Example",null,"java/lang/Object",null); //生成Example的構(gòu)造方法 MethodVisitor mw = cw.visitMethod(ACC_PUBLIC ,"<init>","()V",null,null); mw.visitVarInsn(ALOAD,0); mw.visitMethodInsn(INVOKESPECIAL,"java/lang/Object","<init>","()V",false); mw.visitInsn(RETURN); mw.visitMaxs(0,0); mw.visitEnd(); //生成public static void main(String []args)方法,并生成了main()方法的字節(jié)碼 //要求運(yùn)行時(shí)調(diào)用System.out.println(),并輸出"Hello world": mw = cw.visitMethod(ACC_PUBLIC+ACC_STATIC,"main","([Ljava/lang/String;)V",null,null); mw.visitFieldInsn(GETSTATIC,"java/lang/System","out","Ljava/io/PrintStream;"); mw.visitLdcInsn("Hello world!"); mw.visitMethodInsn(INVOKEVIRTUAL,"java/io/PrintStream","println","(Ljava/lang/String;)V",false); mw.visitInsn(RETURN); mw.visitMaxs(0,0); mw.visitEnd(); //獲取二進(jìn)制表示 byte[] code = cw.toByteArray(); Main m = new Main(); //將class文件載入系統(tǒng),通過反射調(diào)用`main()`方法,輸出結(jié)果 Class<?> mainClass = m.defineClass("Example",code,0,code.length); mainClass.getMethods()[0].invoke(null, new Object[]{null}); } }
到此,相信大家對(duì)“JVM中的Class文件結(jié)構(gòu)”有了更深的了解,不妨來實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。