JVM之阅读一份java class文件 您所在的位置:网站首页 2f4f JVM之阅读一份java class文件

JVM之阅读一份java class文件

2023-11-03 19:48| 来源: 网络整理| 查看: 265

1.简介

要了解JVM的运行机理,从class文件入手最好不过了,下面就简单梳理了一下阅读一个简单的java 字节码文件的过程。个人感受,阅读这个难度到不是很大,重要的是要查阅好Oracle的JVM标准文件以及对工具的使用。

【Oracle官方文档】:https://docs.oracle.com/javase/specs/index.html

使用工具:IDEA

使用的插件:jclasslib 和 BinEnd

JDK:1.8

2.编译java文件

我使用的源代码是最简单的JAVA程序,仅用来梳理整个class文件的框架 。

package com.example.demo; public class helloword { }

这是一个巨简单的文件,主要为了说明编译后的class文件的格式。编译之后的helloword.class文件使用BinEnd工具打开,得到如下(我这里用16进制展示) :

cafe babe 0000 0034 0010 0a00 0300 0d07 000e 0700 0f01 0006 3c69 6e69 743e 0100 0328 2956 0100 0443 6f64 6501 000f 4c69 6e65 4e75 6d62 6572 5461 626c 6501 0012 4c6f 6361 6c56 6172 6961 626c 6554 6162 6c65 0100 0474 6869 7301 001c 4c63 6f6d 2f65 7861 6d70 6c65 2f64 656d 6f2f 6865 6c6c 6f77 6f72 643b 0100 0a53 6f75 7263 6546 696c 6501 000e 6865 6c6c 6f77 6f72 642e 6a61 7661 0c00 0400 0501 001a 636f 6d2f 6578 616d 706c 652f 6465 6d6f 2f68 656c 6c6f 776f 7264 0100 106a 6176 612f 6c61 6e67 2f4f 626a 6563 7400 2100 0200 0300 0000 0000 0100 0100 0400 0500 0100 0600 0000 2f00 0100 0100 0000 052a b700 01b1 0000 0002 0007 0000 0006 0001 0000 0003 0008 0000 000c 0001 0000 0005 0009 000a 0000 0001 000b 0000 0002 000c

我使用jclasslib 解析的class文件整理成xmind参考

BinEnd处理的文件如下 :

3.阅读文件 3.1ClassFile文件结构

Class文件是一组以8位字节为基础单位的二进制流,包含多个数据项目(数据项目的顺序,占用的字节数均由规范定义)。

数据项目分为2种基本数据类型(以及由这两种基本数据类型组成的集合):

1,无符号数,以u1、u2、u4、u8分别代表1个字节、2个字节、4个字节、8个字节的无符号数

2,表,以“_info”结尾,由多个无符号数或其它表构成的复合数据类型

整体文件结构如下(官方提供):

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

下面是一个表格形式说明

类型名称数量u4magic1u2minor_version1u2major_version1u2constant_pool_count1cp_infoconstant_poolconstant_pool_count-1u2access_flags1u2this_class1u2super_class1u2interfaces_count1u2interfacesinterfaces_countu2fields_count1field_infofieldsfields_countu2methods_count1method_infomethodsmethods_countu2attributes_count1attribute_infoattributesattributes_count

由于不包含任何分隔符,故表中的数据项,无论是数量还是顺序,都是被严格限定的

3.2阅读过程

这里的阅读过程按照上面的文件结构,从上到下,从外到内嵌套阅读,要注意阅读顺序

3.2.1 magic(魔数)

每个Class文件的头四个字节称为魔数,它的唯一作用是用来确定该文件是否为一个能被虚拟机接受的Class文件。

3.2.2 minor_version (次版本)

次版本占用u2(两字节),这里minor_version=0x0000

3.2.3 major_version (主版本)

主版本占用u2(两字节),这里major_version=0x0034(翻译成十进制就是52)

(JDK1.5:0x0031,JDK1.6:0x0032,JDK1.7:0x0033,JDK1.8:0x0034)

3.2.4 constant_pool_count (常量池数量)

由于常量池中常量的数目不是固定的,所以在常量池入口首先使用一个2字节长的无符号数constatn_pool_count来代表常量池计数值。

constant_pool_count:占2字节,0x0010,转化为十进制为15,即说明常量池中有15个常量(常量池的计数是从1开始的,其它集合类型均从0开始),索引值为1~15。第0项常量具有特殊意义,如果某些指向常量池索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义,这种情况可以将索引值置为0来表示

参照jclasslib生成的结果:

3.2.5 constant_pool[constant_pool_count-1] (常量池常量)

常量池中的常量索引最大为constant_pool_count-1,即例如上面有16个常量,最大索引是15

表类型数据集合,即常量池中每一项常量都是一个表,共有14种结构各不相同的表结构数据。这14种表都有一个共同的特点,即均由一个u1类型的标志位开始,可以通过这个标志位来判断这个常量属于哪种常量类型,常量类型及其数据结构参见:

类型标志位CONSTANT_Class_info7CONSTANT_Fieldref_info9CONSTANT_Methodref_info10CONSTANT_InterfaceMethodref_info11CONSTANT_String_info8CONSTANT_Integer_info3CONSTANT_Float_info4CONSTANT_Long_info5CONSTANT_Double_info6CONSTANT_NameAndType_info12CONSTANT_Utf8_info1CONSTANT_MethodHandle_info15CONSTANT_MethodType_info16CONSTANT_InvokeDynamic_info18

可以看到,字节码中接下来对应的tag是0a(十进制是10),标志位tag对应的类型是:CONSTANT_Methodref_info,

CONSTANT_Methodref_info对应的文件结构如下:

CONSTANT_Methodref_info { u1 tag; #0a u2 class_index; #00 03 u2 name_and_type_index; #00 0d }

然后开始对CONSTANT_Methodref_info文件进行class文件的解析,如上面所示:class_index和name_and_type_index都是引用常量池,可以从jclasslib的处理结果看出

然后再分别取对应,即可以发现刚好印证了我们的想法。

关于常量池这里不再赘述过多,可以参考:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4

进行后面的解析,这里贴出关于常量池的跟踪结果:

0a #表开始的u1类型标志位 tag=10,对应CONSTANT_Methodref_info表 00 03 #里面具体的引用#3 00 0d #里面具体的引用#13 07 #表开始的u1类型标志位 tag=7,对应CONSTANT_Class_info表 000e #里面的具体引用#14 07 #表开始的u1类型标志位 tag=7,对应CONSTANT_Class_info表 00 0f #里面的具体引用#15 01 00 06 3c69 6e69 743e #[04]表开始的u1类型标志位 tag=1,对应CONSTANT_Utf8_info表 内容: 01 00 0328 2956 #[05]内容:()V 01 00 0443 6f64 65 #[06]内容:Code 01 00 0f 4c69 6e65 4e75 6d62 6572 5461 626c 65 #[07]内容:LineNumberTable 01 00 12 4c6f 6361 6c56 6172 6961 626c 6554 6162 6c65 #[08]内容:LocalVariableTable 01 00 0474 6869 7301 0 #[09]内容:this 01 c 4c63 6f6d 2f65 7861 6d70 6c65 2f64 656d 6f2f 6865 6c6c 6f77 6f72 643b #[10]内容:Lcom/example/demo/helloword; 01 00 0a53 6f75 7263 6546 696c 65 #[11]内容:SourceFile 01 000e 6865 6c6c 6f77 6f72 642e 6a61 7661 #[12]内容:helloword.java 0c 00 04 00 05 #[13]里面的具体引用#4 #5 01 00 1a 636f 6d2f 6578 616d 706c 652f 6465 6d6f 2f68 656c 6c6f 776f 7264 #[14]内容:com/example/demo/helloword 01 00 106a 6176 612f 6c61 6e67 2f4f 626a 6563 74 #[15]内容:java/lang/Object 3.2.6 access_flags (访问标志位)

访问标志位占用u2(2个字节),关于访问标志位的对应表如下

标志名称标志值含义ACC_PUBLIC0x0001是否为public类型ACC_FINAL0x0010是否被声明为final,只有类可设置ACC_SUPER0x0020是否允许使用invokespecial字节码指令,JDK1.2以后编译出来的类这个标志为真ACC_INTERFACE0x0200标识这是一个接口ACC_ABSTRACT0x0400是否为abstract类型,对于接口和抽象类,此标志为真,其它类为假ACC_SYNTHETIC0x1000标识别这个类并非由用户代码产生ACC_ANNOTATION0x2000标识这是一个注解ACC_ENUM0x4000标识这是一个枚举

查看字节码中的标志位如下:

访问标志位是:0x0021,从上面的标志位映射表中找不到对应的数值,这是什么原因的?先使用javap -v xx.class文件

E:\projs\demo\target\classes\com\example\demo>javap -v helloword.class Classfile /E:/projs/demo/target/classes/com/example/demo/helloword.class Last modified 2020-5-30; size 286 bytes MD5 checksum b2654afcd52ddbe82eecd1b919012f4e Compiled from "helloword.java" public class com.example.demo.helloword minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #3.#13 // java/lang/Object."":()V #2 = Class #14 // com/example/demo/helloword #3 = Class #15 // java/lang/Object #4 = Utf8 #5 = Utf8 ()V #6 = Utf8 Code #7 = Utf8 LineNumberTable #8 = Utf8 LocalVariableTable #9 = Utf8 this #10 = Utf8 Lcom/example/demo/helloword; #11 = Utf8 SourceFile #12 = Utf8 helloword.java #13 = NameAndType #4:#5 // "":()V #14 = Utf8 com/example/demo/helloword #15 = Utf8 java/lang/Object { public com.example.demo.helloword(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: return LineNumberTable: line 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/example/demo/helloword; } SourceFile: "helloword.java"

从这里可以看出对应的flags: ACC_PUBLIC, ACC_SUPER是两个权限,结果就来了,这里使用或运算进行计算:测试类的访问标志为ACC_PUBLIC | ACC_SUPER = 0x0001 | 0x0020 =1 | 32 =33 = 0x0021

3.2.7 this_class

官方文档解释如下:

The value of the this_class item must be a valid index into the constant_pool table. The constant_pool entry at that index must be a CONSTANT_Class_info structure (§4.4.1) representing the class or interface defined by this class file.

这里是引用常量池的,对应的值如下:

对应的值是:0x0002,从jclasslib结果中可以看到 ,结果就是自己

3.2.8 super_class

官方文档解释如下:

For a class, the value of the super_class item either must be zero or must be a valid index into the constant_pool table.

也是从常量池中的引用过来的

应用的是常量池中的0x0003,查看jclasslib结果

3.2.9 interfaces_count (接口数量)

接口数量为:0x0000,这个为0,后面的interfaces[interfaces_count];也就不存在了,直接看fields_count

3.2.10 fields_count (字段数量)

字段数量也是0x0000,跳过fields[fields_count]直接看方法数量methods_count

3.2.11 methods_count (方法数量)

从反编译的字节码可以看出里面包含一个默认构造方法

// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package com.example.demo; public class helloword { public helloword() { } }

所以数量应该是1

3.2.12 method_info (方法详情)

方法表的结构如下:

method_info { u2 access_flags; #00 01 u2 name_index;#00 04 u2 descriptor_index;#00 05 u2 attributes_count;#00 01 attribute_info attributes[attributes_count]; }

方法表的access_flag对应关系如下:

标志名称标志值含义ACC_PUBLIC0x0001字段是否为publicACC_PRIVATE0x0002字段是否为privateACC_PROTECTED0x0004字段是否为protectedACC_STATIC0x0008字段是否为staticACC_FINAL0x0010字段是否为finalACC_SYNCHRONIZED0x0020字段是否为synchronizedACC_BRIDGE0x0040方法是否是由编译器产生的桥接方法ACC_VARARGS0x0080方法是否接受不定参数ACC_NATIVE0x0100字段是否为nativeACC_ABSTRACT0x0400字段是否为abstractACC_STRICTFP0x0800字段是否为strictfpACC_SYNTHETIC0x1000字段是否为编译器自动产生

name_index和descriptor_index都是引用常量池的内容,这里不赘述,验证方法同上

attributes_count属性有一个,接下来看属性详情attribute_info,

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

从上面可以看到只有一个属性,接下来应该是属性attribute_name_index

引用常量池#6,即只有Code属性

接下来attribute_length有4个字节00 0000 2f即长度47,可以从jclasslib结果看出

然后查看Code属性Code_attribute

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

对应的结果如下:

00 06 #attribute_name_index #6, 即Code 00 0000 2f #code_length 47 00 01 #操作数栈深度为1 00 01 #方法局部变量占用的空间为1 00 0000 05 #接下来又5个字节的指令,见下 2a #指令:aload_0 b7 0001 #指令:invokespecial 引用#1 b1 #指令:return 0000 #Code属性异常表集合为空 0002 #说明Code属性带有2个属性

code通过jclasslib可以看到有2个属性LineNumberTable和local_variable_info

#LineNumberTable 属性结构 LineNumberTable_attribute { u2 attribute_name_index; u4 attribute_length; u2 line_number_table_length; { u2 start_pc; u2 line_number; } line_number_table[line_number_table_length]; } #LocalVariableTable属性结构 LocalVariableTable_attribute { u2 attribute_name_index; u4 attribute_length; u2 local_variable_table_length; { u2 start_pc; u2 length; u2 name_index; u2 descriptor_index; u2 index; } local_variable_table[local_variable_table_length]; }

对应的分析结果如下:

0007 #Code属性第一个属性的属性名称,指向常量池中第7个常量:LineNumberTable 0000 0006 #表示LineNumberTable属性值所占字节长度为6 0001 #即该line_number_table中只有一个line_number_info表 0000 #start_pc为0x0000 0003 #line_number为0x0003 ===到这里line_number属性结束 0008 #Code属性第二个属性的属性名,指向常量池中第8个常量 0000 000c #该属性值所占的字节长度为12 0001 #说明local_variable_table中只有一个local_variable_info表 0000 #start_pc为0x0000 0005 #length为0x0005 0009 #name_index为0x0009,指向常量池中第9个常量 this 000a #descriptor_index为0x000a,指向常量池中第10个常量:Lcom/example/demo 0000 #index 3.2.13 attributes_count 和attribute_info (属性数量)

可以看到只有一个属性,属性的结构如下:

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

分析结果如下:

000b #attribute_name_index #11 SourceFile 0000 0002 #attribute_length 长度为2 000c #sourcefile_index #12 helloword.java

从jclasslib的结果可以印证这一点

 

完整的解读结果 整个文件的解读结果如下: cafe babe #魔数 0000 #小版本 0034 #大版本 0010 #counstant_pool_count 15个 0a #表开始的u1类型标志位 tag=10,对应CONSTANT_Methodref_info表 00 03 #里面具体的引用#3 00 0d #里面具体的引用#13 07 #表开始的u1类型标志位 tag=7,对应CONSTANT_Class_info表 000e #里面的具体引用#14 07 #表开始的u1类型标志位 tag=7,对应CONSTANT_Class_info表 00 0f #里面的具体引用#15 01 00 06 3c69 6e69 743e #[04]表开始的u1类型标志位 tag=1,对应CONSTANT_Utf8_info表 内容: 01 00 0328 2956 #[05]内容:()V 01 00 0443 6f64 65 #[06]内容:Code 01 00 0f 4c69 6e65 4e75 6d62 6572 5461 626c 65 #[07]内容:LineNumberTable 01 00 12 4c6f 6361 6c56 6172 6961 626c 6554 6162 6c65 #[08]内容:LocalVariableTable 01 00 0474 6869 7301 0 #[09]内容:this 01 c 4c63 6f6d 2f65 7861 6d70 6c65 2f64 656d 6f2f 6865 6c6c 6f77 6f72 643b #[10]内容:Lcom/example/demo/helloword; 01 00 0a53 6f75 7263 6546 696c 65 #[11]内容:SourceFile 01 000e 6865 6c6c 6f77 6f72 642e 6a61 7661 #[12]内容:helloword.java 0c 00 04 00 05 #[13]里面的具体引用#4 #5 01 00 1a 636f 6d2f 6578 616d 706c 652f 6465 6d6f 2f68 656c 6c6f 776f 7264 #[14]内容:com/example/demo/helloword 01 00 106a 6176 612f 6c61 6e67 2f4f 626a 6563 74 #[15]内容:java/lang/Object 0021 # ACCESS_FLAG:0021 0002 # THIS_CLASS:引用cp #2 0003 # SUPER_CLASS:引用cp #3 00 00 #Interface_count:0 00 00 #Field_count 00 01 #method_count 00 01 #access_flag 00 04 #name_index 00 05 #descriptor_index 00 01 #attribute_count 00 06 #attribute_name_index #6, 即Code 00 0000 2f #code_length 47 00 01 #操作数栈深度为1 00 01 #方法局部变量占用的空间为1 00 0000 05 #接下来又5个字节的指令,见下 2a #指令:aload_0 b7 0001 #指令:invokespecial 引用#1 b1 #指令:return 0000 #Code属性异常表集合为空 0002 #说明Code属性带有2个属性 0007 #Code属性第一个属性的属性名称,指向常量池中第7个常量:LineNumberTable 0000 0006 #表示LineNumberTable属性值所占字节长度为6 0001 #即该line_number_table中只有一个line_number_info表 0000 #start_pc为0x0000 0003 #line_number为0x0003 ===到这里line_number属性结束 0008 #Code属性第二个属性的属性名,指向常量池中第8个常量 0000 000c #该属性值所占的字节长度为12 0001 #说明local_variable_table中只有一个local_variable_info表 0000 #start_pc为0x0000 0005 #length为0x0005 0009 #name_index为0x0009,指向常量池中第9个常量 this 000a #descriptor_index为0x000a,指向常量池中第10个常量:Lcom/example/demo 0000 #index 0001 #attributes_count=1 000b #attribute_name_index #11 SourceFile 0000 0002 #attribute_length 长度为2 000c #sourcefile_index #12 helloword.java

官方文档



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有