溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

JVM中的Class文件結(jié)構(gòu)

發(fā)布時(shí)間:2021-09-17 09:27:00 來源:億速云 閱讀:109 作者:chen 欄目:編程語言

本篇內(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ù)

魔數(shù)(Magic Number)作為Class的標(biāo)志,用來告訴JVM這是一個(gè)Class文件,魔數(shù)是一個(gè)4字節(jié)的無符號(hào)整數(shù),固定為0xCAFEBABE。如果一個(gè)Class文件不以0xCAFEBABE開頭,那么會(huì)拋出如下錯(cuò)誤:

JVM中的Class文件結(jié)構(gòu)

Linux下可以直接使用vim打開class文件進(jìn)行查看,比如需要打開一個(gè)Test.class文件,可以輸入如下命令:

vim -b Test.class
:%!xxd

切換到十六進(jìn)制后就可以看到魔數(shù)了:

JVM中的Class文件結(jié)構(gòu)

 版本

魔數(shù)后面緊跟著Class的小版本和大版本號(hào),這表示當(dāng)前Class文件是由哪個(gè)版本的編譯期產(chǎn)生的。小版本和大版本后都是占用兩個(gè)字節(jié),比如下圖:

JVM中的Class文件結(jié)構(gòu)

  • 0000是小版本號(hào)

  • 0037是大版本號(hào),十進(jìn)制為55,也就是對(duì)應(yīng)JDK 11版本的編譯期

 常量池

在版本號(hào)后面,緊跟著就是常量池的數(shù)量以及若干個(gè)常量池表項(xiàng):

JVM中的Class文件結(jié)構(gòu)

JVM中的Class文件結(jié)構(gòu)

其中每一個(gè)常量池表項(xiàng)都具有標(biāo)簽屬性:

JVM中的Class文件結(jié)構(gòu)

對(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)記

訪問標(biāo)記使用兩個(gè)字節(jié)表示,用于表明該類的訪問信息,比如public/abstract等,對(duì)應(yīng)關(guān)系如下:

  • ACC_PUBLIC0x0001,表示public

  • ACC_FINAL0x0010,表示是否為final

  • ACC_SUPER0x0020,表示使用增強(qiáng)的方法調(diào)用父類的方法

  • ACC_INTERFACE0x0200,表示是否為接口

  • ACC_ABSTRACT0x0400,表示是否為抽象類

  • ACC_SYNTHETIC0x1000,由編譯期產(chǎn)生的類,沒有源碼對(duì)應(yīng)

  • ACC_ANNOTATION0x2000,表示是否是注釋

  • ACC_ENUM0x4000,表示是否為枚舉

當(dāng)前類、父類和接口

格式如下:

u2             this_class;                                    
u2             super_class;
u2             interfaces_count;
u2             interfaces[interfaces_count];

其中this_classsuper_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ù)組表示的信息
}

 方法

1 方法基本結(jié)構(gòu)

方法的格式如下:

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ù)組
}

2 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

2.1 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>元組
}

2.2 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];         
}

2.3 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});
    }
}

JVM中的Class文件結(jié)構(gòu)

到此,相信大家對(duì)“JVM中的Class文件結(jié)構(gòu)”有了更深的了解,不妨來實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問一下細(xì)節(jié)

免責(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)容。

jvm
AI