您的当前位置:首页正文

Java字节码方法表与属性表深度剖析

来源:华佗小知识
Java字节码⽅法表与属性表深度剖析

⽅法表:

在上⼀次咱们已经分析到了字段信息了,如下:

紧接着就是⽅法相关的信息了:

⽽它展开之后的结构为:

所以往后数2个字节,看⼀下⽅法的总数:

3个⽅法,可咱们只定义了两个⽅法呀:

因为编译器会为我们⽣成⼀个默认的构造⽅法,所以就3个了,那每个⽅法的具体信息是啥呢?它是⼀个method_info类型的,如下:

也就是⽅法表,当然也有它⾃⼰的⼀个结构,下⾯来看⼀下:

access_flags:占⽤两个字节,表⽰访问标记。

name_index:占⽤两个字节,名字索引,指向的是常量池。descriptor_index:占⽤两个字节,描述索引,指赂的是常量池。

attributes_count:占⽤两个字节,属性个数,如果为0,则下⾯的属性表就不显⽰了。attributes::属性表。⽤结构形式来表⽰:

那按照上⾯的表先来看第⼀个⽅法的访问标记,往后读两个字节:

查看下访问修饰符表,对应于:

表⽰是⼀个public的⽅法,接下来两个字节则表⽰⽅法名字索引,⾛着:

对应常量池:

再往下⼆个字节则表⽰描述符索引:

对应常量池:

说明该⽅法是⼀个默认构造⽅法。从javap -verbose中也能对应上:

属性表:

接下来⼆个字节为属性个数:

表⽰有⼀个属性,所以属性表中的个数也为1,⽽属性表是attribute_info类型,很显然也有它⾃⼰的结构,那长啥样呢?

attribute_name_index:占2个字节,表⽰属性名字的索引,指向常量池。attribute_length:占4个字节,表⽰属性的长度。

info[attribute_length]:占1个字节,表⽰具体的信息。依照上⾯的顺序,先数2个字节:

对应常量池:

其实在javap -verbose中也能看到每个⽅法都有⼀个Code字样,如下:

Constant pool:

#1 = Methodref #4.#20 // java/lang/Object.\"\":()V #2 = Fieldref #3.#21 // com/jvm/bytecode/MyTest1.a:I #3 = Class #22 // com/jvm/bytecode/MyTest1 #4 = Class #23 // java/lang/Object #5 = Utf8 a #6 = Utf8 I

#7 = Utf8 #8 = Utf8 ()V #9 = Utf8 Code

#10 = Utf8 LineNumberTable #11 = Utf8 LocalVariableTable #12 = Utf8 this

#13 = Utf8 Lcom/jvm/bytecode/MyTest1; #14 = Utf8 getA #15 = Utf8 ()I #16 = Utf8 setA #17 = Utf8 (I)V

#18 = Utf8 SourceFile #19 = Utf8 MyTest1.java

#20 = NameAndType #7:#8 // \"\":()V #21 = NameAndType #5:#6 // a:I

#22 = Utf8 com/jvm/bytecode/MyTest1 #23 = Utf8 java/lang/Object{

public com.jvm.bytecode.MyTest1(); descriptor: ()V

flags: ACC_PUBLIC Code:

stack=2, locals=1, args_size=1 0: aload_0

1: invokespecial #1 // Method java/lang/Object.\"\":()V 4: aload_0 5: iconst_1

6: putfield #2 // Field a:I 9: return

LineNumberTable: line 3: 0 line 4: 4

LocalVariableTable:

Start Length Slot Name Signature

0 10 0 this Lcom/jvm/bytecode/MyTest1; public int getA(); descriptor: ()I

flags: ACC_PUBLIC Code:

stack=1, locals=1, args_size=1 0: aload_0

1: getfield #2 // Field a:I 4: ireturn

LineNumberTable: line 7: 0

LocalVariableTable:

Start Length Slot Name Signature

0 5 0 this Lcom/jvm/bytecode/MyTest1; public void setA(int); descriptor: (I)V

flags: ACC_PUBLIC Code:

stack=2, locals=2, args_size=2 0: aload_0 1: iload_1

2: putfield #2 // Field a:I 5: return

LineNumberTable: line 11: 0 line 12: 5

LocalVariableTable:

Start Length Slot Name Signature

0 6 0 this Lcom/jvm/bytecode/MyTest1; 0 6 1 a I}

SourceFile: \"MyTest1.java\"

那它表⽰啥意思呢?其实是表⽰⽅法执⾏的代码,指的是:

当然啦在字节码⽂件中不可能是跟源⽂件中看到的⼀样,⽽是通过了⼀些助记符进⾏了处理,如下:

这个在未来进⾏详细学习的,好,继续来分析属性,接下来4个字节表⽰属性的长度,如下:

说明属性的长度为56,然后最后⼀个字节表⽰info信息,也就是code的具体信息,这块是⽐较复杂的,下⾯先来了解⼀些理论:

JVM预定义了部分attribute,但是编译器⾃⼰也可以实现⾃⼰的attribute写⼊class⽂件⾥,供运⾏时使⽤。不同的attribute通过attribute_name_index来区分。其中JVM预定义的attribute为如下表:

Code结构:

这部分东东是⽐较多的,这次只先对其结构有个初步了解既可,它的作⽤是保存该⽅法的结构,如所对应的字节码:

attribute_length表⽰attribute所包含的字节数,不包含attribute_name_index和attribute_length字段。max_stack表⽰这个⽅法运⾏的任何时刻所能达到的操作数栈的最⼤深度。

max_locals表⽰⽅法执⾏期间创建的局部变量的数⽬,包含⽤来表⽰传⼊的参数的局部变量。code_length表法该⽅法所包含的字节码的字节数以及具体的指令码。具体的字节码既是该⽅法被调⽤时,虚拟机所执⾏的字节码。exception_table:这⾥存放的是处理异常的信息。

第⼀个exception_table表项由start_pc、end_pc、handler_pc、catch_type组成。

start_pc和end_pc表⽰在code数组中的从start_pc到end_pc处(包含start_pc,不包含end_pc)的指令抛出的异常会由这个表项来处理。

handler_pc表⽰处理异常的代码的开始处。catch_type表⽰会被处理的异常类型,它指向常量池⾥的⼀个异常类。当catch_type为0时,表⽰处理所有的异常。

这么多陌⽣的字段,直接晕掉,⽊要着急,先有个⼤概了解,在未来学习中会吃透它的,好,先来回到字节码中继续分析,其中code中属性的长度为56:

接下来2个字节表⽰max_stack:

再接下来2个字节表⽰max_locals:

对应javap -verbose:

接下来4个字节表⽰code的长度:

code_length=10,⽽此时发现在javap -verbose中貌似⽊有找到对应的:

那接下来的分析没有了参照就不知道我们⾃⼰分析的对不对了,对于学习效果会⼤打折扣了,此时就得借助于另外⼀个⼯具来参照了,该⼯具为jclasslib,gitbub地址:,它显⽰的信息就会⽐javap -verbose要详细很多,访问⼀下官⽹:

它包含独⽴的软件和IntelliJ IDEA插件化的⽅式,所以都装⼀下,先下载mac安装包:

具体安装就不概述了,装好之后⽤它来打开我们的字节码⽂件既可,长这样:

同时可以给IDE装上插件,更加便于分析,如下:

安装好之后,直接就可以在当前打开的java⽂件中执⾏这个菜单选项既可:

看到的效果跟独⽴的软件看到的是⼀样的,好,下⾯来⽤这个新⼯具来瞅⼀眼看到的信息:

对⽐下javap -verbose:

差不多,不过jclasslib⼯具可以看到JDK的版本,接下来就是常量池:

但实际是只有23个,展开看⼀下:

对于javap -versbose:

明显要丰富许多,继续往下看:

其中是可以直接点击链到对应的常量池的,如下:

接着就是常量池的信息了:

展开之后,索引都能链接上去,⾮常之⽅便:

接着就是接⼝信息,⽬前⽊有接⼝:

然后就到了字段信息了,⽬前只有⼀个字段:

然后再是⽅法信息,有三个⽅法:

点击其中⼀个看⼀下:

有code信息:

跟javap -versbose是对应上的:

最后是附加信息:

LineNumberTable:这个属性⽤来表⽰code数组中的字节码和Java代码⾏数之间的关系。这个属性可以⽤来在调试的时候定位代码执⾏的⾏数。⽐如说程序抛异常了,⽽程序执⾏的是字节码⽂件,怎么我们就能看到具体报错在源码中的⾏数呢,其实就是通过该信息做到的。⽽它的结构体为:

跟javap -verbose中是能对应上的:

最后则是类的属性了:

可见jclasslib的结构跟咱们理论上看到的是⼀模⼀样的,所以有了它也能让我们在未来学习code这块的结构更加清晰,这是javap -verbose不能达到的。

因篇幅问题不能全部显示,请点此查看更多更全内容