jvm原理(27)Java字节码方法表与属性表深度剖析

上一节说到成员变量,这一节说一下方法表
图一:
这里写图片描述
图二:
这里写图片描述
图三:
这里写图片描述
行号00000120 开始就是方法表的开始,刚开始2个字节是方法的数量:00 03 是三个方法(无参构造器、变量a的get和set方法)
方法表:

类型名称数量
u2access_flags1
u2name_index1
u2descriptor_index1
u2attributes_count1
u2attributesattributes_count

每一个方法都是这样的一个结构。
00 03后边是 access_flags (00 01)根据图二表示的是public的修饰,name_index:00 07 ,descriptor_index:00 08 在常量池里边是:[ #7 = Utf8 <init>] 、[#8 = Utf8 ()V] ;
然后就是方法的attributes_count 和attributes :
attributes_count :比如方法执行的字节码是什么?方法的行号表是什么?局部变量表是什么?这些信息的sum。
attributes_count :00 01 ,方法只有一个属性,但是attribute_info 的结构是什么呢?为此我们需要知道attribute_info 的结构,如下:
这里写图片描述
attributes_count :00 01 后边的2个字节就是attribute_name_index:00 09(常量池的【#9 = Utf8 Code】).
这里写图片描述
attributes_length: 00 00 00 38 (十进制56)
info:是后面的真正的属性的内容,即方法的字节码。
方法的属性结构:
这里写图片描述
- JVM预定义了部分attribute,但是编译器自己也可以实现自己的attribute写入class文件
里, 供运行时使用。
- 不同的attribute通过attribute_name_index来区分。
- Code结构:
code attribute的作用是保存该方法的结构,如所对应的字节码:
Code_attribute{
u2 attribute_name_index; //00 09
u2 attribute_length; // 00 00 00 38
u2 max_stack; //00 02
u2 max_locals; //00 01
u4 code_length; //00 00 00 0A
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];
}
attribute_length: 表示attribute所包含的字节数,不包含attribute_name_index和attribute_lenght字段。
max_stack表示这个方法运行的任何时刻所能达到的操作数栈的最大深度。
max_locals表示方法执行运行期间创建的局部变量的数目,包含用来表示传入的参数的局部变量。
code_length表示该方法所包含的字节码的字节数以及具体的指令码
具体字节码即是该方法被调动时,虚拟机所执行的字节码。
code_length是00 00 00 0A(十进制10),往后数10个字节:
2AB700012A04B50002B1
如图:
这里写图片描述
这是个字节对应的就是 jclasslib插件反编译的如下信息:
这里写图片描述
这里边有很多助记词。助记词和16进制编码在jvm里边有已经定义好的映射关系。
单击bytecode里边的load0进入oracle的官方网站,load0在doc里边对应的是16进制的0x2a,而在我们的class文件里边也是2A 。

aload_0 = 42 (0x2a)
aload_1 = 43 (0x2b)
aload_2 = 44 (0x2c)
aload_3 = 45 (0x2d)

同样的invokespecial 在doc里边对应的是16进制是0xb7

invokespecial = 183 (0xb7)

invokespecial 可以简单理解为调用父类的构造方法,invokespecial 是带有参数的,参数就是invokespecial 后边的2个字节描述的信息:00 01 ,而00 01是常量池里边的某个常量,就是:

#1 = Methodref          #4.#20         // java/lang/Object."<init>":()V

MyTest1的父类是Object,MyTest1构造器会首先调用父类的构造器,这个和我们对Java学习的理解不谋而合。
然后接下来又是 aload_0 ,以及 iconst_1 助记词,在doc里边有7个:

iconst_m1 = 2 (0x2)
iconst_0 = 3 (0x3)
iconst_1 = 4 (0x4)
iconst_2 = 5 (0x5)
iconst_3 = 6 (0x6)
iconst_4 = 7 (0x7)
iconst_5 = 8 (0x8)

我们的iconst_1对应的1进制是0x04 。
接下来是putfield,它是带参数的。

putfield = 181 (0xb5)

putfield 对应的16进制是0xb5,我们的二进制文件也是0xb5,没有问题,然后B5后边的是putfield 的参数:00 02 ,00 02在常量池里边的是:

2 = Fieldref   #3.#21         // com/twodragonlake/jvm/bytecode/MyTest1.a:I

putfield 的作用是 Set field in object 即 赋值,给谁赋值? 给com/twodragonlake/jvm/bytecode/MyTest1.a:I 赋值,赋值为多少?就是推到栈顶的上一个助记词iconst_1。
之后是 return 助记词:

return = 177 (0xb1)

return 的1进制是B1,我们的class文件当中也是B1。
整个init方法完成了对a的赋值。
接下来的00 00 是异常表:
这里写图片描述
由于构造方法没有异常所以是0。
exception_table,这里存放的是处理异常的信息
每个exception_table表项由start_pc, end_pc,handler_pc,catch_type组成。
start_pc h=和end_pc表示在code数组中的从start_pc到end_pc处包含start_pc,不包含end_pc)的指令抛出的异常会由这个表项来处理。
handler_pc表示处理异常的代码的开始处,catch_type表示会被处理的异常类型,它指向量池里的一个异常类。当catch_type为0时,表示处理所有的异常。
然后是u2 attributes_count; 也就是后边的: 00 02 ,意味着构造方法有2个属性,之后的00 0A(十进制10)是属性的索引,在常量池里边是10号常量:

#10 = Utf8               LineNumberTable

即,行号表:
附加属性
lineNumberTable:这个属性用来表示code数组中的字节码和java代码行数之间的关系。
这个属性可以用来在调试的时候定位代码行的行数。这是属性在调试的时候比较有用,就是抛出异常的代码的行数。
LineNumberTable_attribute{
u2 attribute_name_index; //00 0A
u4 attribute_length; // 00 00 00 0A
u2 line_number_table_length; // 00 002 表示2个
{
u2 start_pc;
u2 line_number;
} line_number_table[line_number_table_length]
}

attribute_name_index z 是00 0A ,attribute_length是00 00 00 0A ,也即是说往后数10个字节都是attribute的整体,后边的10个字节是:0002 0000 001A 0004 001B 。0002 是长度,既是2个,后边的0000 001A 0004 001B 2个字节是一对,0000 对应 001A(十进制26) ;0004(十进制4) 对应001B(十进制27) ;
这里写图片描述
接着后边的是00 0B(十进制11) 对应常量池:

#11 = Utf8               LocalVariableTable

即,局部变量表。
局部变量表栈的字节的长度:00 00 00 0C(十进制12),即后边的12个字节就是局部变量表的整体,即:0001 0000 000A 000C 000D 0000
这里写图片描述
首先0001是局部变量的个数是1 ;
0000 是局部变量的开始位置(start PC);
000A 是局部变量的结束位置(length);
000C 是局部变量对应常量池里边的位置是12,即:

 #12 = Utf8               this

构造方法为什么会有this? this,对于实例方法(非static)是默认隐式传递进来的。实例方法至少有一个局部变量,那就是this。
000D是对局部变量的一个描述;即:

#13 = Utf8               Lcom/twodragonlake/jvm/bytecode/MyTest1;

0000 是jdk1.6加入的用来做校验检查的,略过。
到此init方法解析完毕;
剩下的是getA方法
00 01 是访问修饰符,是public的。
00 0E 名字索引, 即:#14 = Utf8 getA
00 0F 描述符索引,即:#15 = Utf8 ()I
00 01 是attributes_count,只有一个。
接着是 00 09 : 名字的索引 #9 = Utf8 Code
00 00 00 2F:属性的长度,十进制是47,后边的47个字节是getA方法Code的整体。
这里写图片描述
00 00 00 2F 之后是
max_stack : 00 01
max_locals : 00 01 局部变量的数目 也是1个,既是this。
code_length: 00 00 00 05
往后数5个字节:
2A(aload_0)
B4(getfield)
00 02 常量 : #2 = Fieldref #3.#21 // com/twodragonlake/jvm/bytecode/MyTest1.a:I
AC ireturn ireturn是返回一个整型。
2AB40002AC 后边的00 00 是异常表,程序没有抛出异常所以是0。
00 02 是属性个数有2个。
00 0A 第一个属性指向第十个常量。#10 = Utf8 LineNumberTable
00 00 00 06 属性长度是6
0001 0000 001E :0001 有一个属性:偏移量0,对应源文件的30行。
这里写图片描述
然后是局部变量表:00 0B 常量池的 #11 = Utf8 LocalVariableTable
00 00 00 0C 是局部变量表字节长度是 13个字节。
0001 0000 0005 000C 000D 0000
0001 有一个局部变量。
0000 开始位置0
0005 结束位置5
000C 局部变量的名字 #12 = Utf8 this
000D 是局部变量的描述:#13 = Utf8 Lcom/twodragonlake/jvm/bytecode/MyTest1;
0000 检验码
最后还有一个setA方法:
这里写图片描述
00 01 是public 描述符
00 10 名字索引 #16 = Utf8 setA
00 11 描述符索引 #17 = Utf8 (I)V
00 01 有一个属性
00 09 属性是 #9 = Utf8 Code
00 00 00 3E 属性的长度 是62
00 00 00 3E 是code的如下信息:
max_stack : 00 02
max_locals : 00 02 ,00 02 局部变量的数目 也是2个,既是this和int a
code_length : 00 00 00 06 往后数6个字节:
2A 1B B5 00 02 B1
这里写图片描述
laload_0 2A
iload_1 1B

iload_0 = 26 (0x1a)
iload_1 = 27 (0x1b)
iload_2 = 28 (0x1c)
iload_3 = 29 (0x1d)

iload_1的参数(给谁赋值):00 02 【#2 = Fieldref #3.#21 // com/twodragonlake/jvm/bytecode/MyTest1.a:I】
B1 return
2A 1B B5 00 02 B1 后边的 00 00 是异常表的长度为0。
然后00 02 是方法有2个属性:
00 0A #10 = Utf8 LineNumberTable
0000 000A 属性表长度是10个字节。
即:
0002 0000 0022 0005 0023
0002 : 有2个对应关系、
0000 0022 : 偏移量0对应 34行。
0005 0023 : 偏移量5对应 35行。
LineNumberTable 结束.
00 0B #11 = Utf8 LocalVariableTable
LocalVariableTable:
00 00 00 16 :长度, 后面32个字节是LocalVariableTable的整体。
00 02 : 局部变量表有2个。
第一个局部变量:
00 00 :开始位置
00 06 : 长度是6
00 0C : 局部变量名字 #12 = Utf8 this
00 0D :局部变量描述 #13 = Utf8 Lcom/twodragonlake/jvm/bytecode/MyTest1;
0000 检验码
第二个局部变量:
0000 开始位置0
00 06 长度是6
00 05 : 局部变量名字 #5 = Utf8 a
00 06: 局部变量描述 #6 = Utf8 I
00 01 : 校验码

剩下的数字节码文件的attributes:
00 01 是只有一个属性
00 12 是第一个属性名字的索引 十进制18 : #18 = Utf8 SourceFile
00 00 00 02长度占据2个字节。
最后的00 13的就是不1个字节。
00 13 是 19 = Utf8 MyTest1.java

至此整个class文件解析完毕。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
为什么要学JVM1、一切JAVA代码都运行在JVM之上,只有深入理解虚拟机才能写出更强大的代码,解决更深层次的问题。2、JVM是迈向高级工程师、架构师的必备技能,也是高薪、高职位的不二选择。3、同时,JVM又是各大软件公司笔试、面试的重中之重,据统计,头部的30家互利网公司,均将JVM作为笔试面试的内容之一。4、JVM内容庞大、并且复杂难学,通过视频学习是最快速的学习手段。课程介绍本课程包含11个大章节,总计102课时,无论是笔试、面试,还是日常工作,可以让您游刃有余。第1章 基础入门,从JVM是什么开始讲起,理解JDK、JRE、JVM的关系,java的编译流程和执行流程,让您轻松入门。第2章 字节码文件,深入剖析字节码文件的全部组成结构,以及javap和jbe可视化反解析工具的使用。第3章 类的加载、解释、编译,本章节带你深入理解类加载器的分类、范围、双亲委托策略,自己手写类加载器,理解字节码解释器、即时编译器、混合模式、热点代码检测、分层编译等核心知识。第4章 内存模型,本章节涵盖JVM内存模型的全部内容,程序计数器、虚拟机栈、本地方法栈、方法区、永久代、元空间等全部内容。第5章 对象模型,本章节带你深入理解对象的创建过程、内存分配的方法、让你不再稀里糊涂。第6章 GC基础,本章节是垃圾回收的入门章节,带你了解GC回收的标准是什么,什么是可达性分析、安全点、安全区,四种引用类型的使用和区别等等。第7章 GC算法与收集器,本章节是垃圾回收的重点,掌握各种垃圾回收算法,分代收集策略,7种垃圾回收器的原理和使用,垃圾回收器的组合及分代收集等。第8章 GC日志详解,各种垃圾回收器的日志都是不同的,怎么样读懂各种垃圾回收日志就是本章节的内容。第9章 性能监控与故障排除,本章节实战学习jcmd、jmx、jconsul、jvisualvm、JMC、jps、jstatd、jmap、jstack、jinfo、jprofile、jhat总计12种性能监控和故障排查工具的使用。第10章 阿里巴巴Arthas在线诊断工具,这是一个特别小惊喜,教您怎样使用当前最火热的arthas调优工具,在线诊断各种JVM问题。第11章 故障排除,本章会使用实际案例讲解单点故障、高并发和垃圾回收导致的CPU过高的问题,怎样排查和解决它们。课程资料课程附带配套项目源码2个159页高清PDF理论篇课件1份89页高清PDF实战篇课件1份Unsafe源码PDF课件1份class_stats字段说明PDF文件1份jcmd Thread.print解析说明文件1份JProfiler内存工具说明文件1份字节码可视化解析工具1份GC日志可视化工具1份命令行工具cmder 1份学习方法理论篇部分推荐每天学习2课时,可以在公交地铁上用手机进行学习。实战篇部分推荐对照视频,使用配套源码,一边练习一遍学习。课程内容较多,不要一次性学太多,而是要循序渐进,坚持学习。      

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值