JVM学习笔记38-42——Java字节码内容剖析

1、使用javap -verbose命令分析一个字节码文件时,将会分析该字节码文件的魔数、版本号、常量池、类信息、类的构造方法、类中的方法信息、类变量与成员变量等信息。

2、魔数:所有的.class字节码文件的前四个字节都是魔数,魔数值为固定值:0xCAFEBABE

3、魔数之后的4个字节为版本信息,前两个字节表示minor version (次版本号),后两个字节表示major version(主版本号)。这里版本号为00 00 00 34,换算成十进制,表示次版本号为0,主版本号为52,所以该版本号为1.8.0,可以通过java -version验证。

4、常量池(constant pool):紧接着主版本号之后的就是常量池入口。一个java类中定义的很多信息都是由常量池来维护和描述的,可以将常量池看作是Class文件的资源仓库,比如Java类中定义的方法与变量信息,都是存储在常量池中。常量池中主要存储两类常量:字面量与符号引用。字面量如文本字符串,Java中声明为final的常量值等,而符号信用如类和接口的全局限定名,字段的名称和描述符,方法的名称和描述符等。

5、常量池的总体结构:Java类所对应的常量池主要由常量池数量与常量池数组这两部分共同构成。常量池数量紧跟在主版本号后面,占据两个字节;常量池数组紧跟在常量池数量之后。常量池数组与一般的数组不同的是,常量池中不同的元素的类型,结构都是不同的,长度当然也就不同,但是,每一种元素的第一种数据都是一个u1类型,该字节是个标志位,占据一个字节。JVM在解析常量池时,会根据这个u1类型来获取元素的具体类型。值得注意的是,常量池数组中元素的个数 = 常量池数 - 1 (其中0暂时不使用),目的是满足某些常量池索引值的数据在特定情况下需要表达【不引用任何一个常量池】的含义;根本原因在于,索引为0也是一个常量(保留常量),只不过它不位于常量表中,这个常量就对应null值;所以常量池的索引从1而非0开始。

6、在JVM规范中,每个变量\字段都有描述信息,描述信息主要的作用是描述字段的数据类型、方法的参数列表(包括数量、类型与顺序)与返回值。根据描述符规则,基本数据类型和代表无返回值的void类型都用一个大写字符来表示,对象类型则使用字符L加对象的全限定名称来表示。为了压缩字节码文件的体积,对于基本数据类型,JVM都只使用一个大写字母来表示,如下所示:B - byte,C - char, D - double, F - float, I - int,J - long, S - short, Z - boolean, V - void, L - 对象类型,如Ljava/lang/String;

7、对于数组类型来说,每一个维度使用一个前置的[来表示,如int [] 被记录为[I, String[][] 被记录为[[Ljava/lang/String;

8、用描述符描述方法时,按照先参数列表,后返回值的顺序来描述。参数列表按照参数的严格顺序放在一组()之内,如方法 String getRealnamebyIdAndNickname(int id, String name)的描述符为:(I,Ljava/lang/String) Ljava/lang/String;

常量池对照表

cafe babe 魔数

0000 次版本号 0034 主版本号

0018 常量值数量 0a u1标记 00 04 u2 00 14 u2 09  常量值的信息需要对照javap -verbose  的内容看#数字表示索引
0003 0015 0700 1607 0017 0100 0161 0100
0149 0100 063c 696e 6974 3e01 0003 2829
5601 0004 436f 6465 0100 0f4c 696e 654e
756d 6265 7254 6162 6c65 0100 124c 6f63
616c 5661 7269 6162 6c65 5461 626c 6501
0004 7468 6973 0100 214c 636f 6d2f 7973
6875 6f6f 2f6a 766d 2f62 7974 6563 6f64
652f 4d79 5465 7374 313b 0100 0467 6574
4101 0003 2829 4901 0004 7365 7441 0100
0428 4929 5601 000a 536f 7572 6365 4669
6c65 0100 0c4d 7954 6573 7431 2e6a 6176
610c 0007 0008 0c00 0500 0601 001f 636f
6d2f 7973 6875 6f6f 2f6a 766d 2f62 7974
6563 6f64 652f 4d79 5465 7374 3101 0010
6a61 7661 2f6c 616e 672f 4f62 6a65 6374  以上就是常量池的内容,根据最开始的数量来判断结束

类的访问控制权限:
0021 0x0020和0x0001的并集,表示ACC_PUBLIC与ACC_SUPER

类名索引:

0003 是一个索引指向常量池的#03

父类名索引:

0004 索引指向父类的名字

接口信息:

0000 接口数量,因为数量是0,接口表就不会出现

域数量:代码里只有一个private int a = 1

0001 成员变量数量

域信息field_info:

0002 access_flag 表示 private 0005 name_index 0006 discriptor_index 描述符索引 0000 attributes_count 说明没有属性,attributes_info也不会出现

方法信息:

0003 方法的数量,还有一个默认构造方法所以加起来是3个

方法表:

0001 access_flag表示public 0007 name_index 0008 discriptor_index  0001 attributes_count

0009  0000 0038  00  02 0001 0000 000a 2ab7 0001 2a04
b500 02b1 0000 0002 000a 0000 000a 0002
0000 0007 0004 0009 000b 0000 000c 0001
0000 000a 000c 000d 0000 0001 000e 000f
0001 0009 0000 002f 0001 0001 0000 0005
2ab4 0002 ac00 0000 0200 0a00 0000 0600
0100 0000 0c00 0b00 0000 0c00 0100 0000
0500 0c00 0d00 0000 0100 1000 1100 0100
0900 0000 3e00 0200 0200 0000 062a 1bb5
0002 b100 0000 0200 0a00 0000 0a00 0200
0000 1000 0500 1100 0b00 0000 1600 0200
0000 0600 0c00 0d00 0000 0000 0600 0500
0600 0100 0100 1200 0000 0200 13

 

Class字节码中有两种数据类型

  • 字节数量直接量:这是基本的数据类型。共细分为u1,u2,u4,u8四种,分别代表连续的1个字节,2个字节,4个字节,8个字节组成的整体数据。
  • 表(数组):表是由多个基本数据或其他表,按照既定顺序组成的大的数据集合。表是由结构的,它的结构体现在:组成表的成分所在的位置和顺序都是已经严格定义好的。
  • 上面的表中描述了11种数据类型的结构,其实在jdk1.7之后增加了3种(CONSTANT_MethodHandle_info,CONSTANT_MethodType_info以及CONSTANT_InvokeDynamic_info)对应一些动态调用。这样一共是14种

Access_Flag 访问标志

访问标志信息包括该Class文件是类还是接口,是否被定义为public,是否是abstract,如果是类,是否被声明为final,通过上面的源代码,我们知道该文件是类并且是public。

我们的后面两个字节是0021,在表中没有对应的标志,是因为

0x0021 是 0x0020和0x0001的并集,表示ACC_PUBLIC与ACC_SUPER

字段表用于描述类和接口中声明的变量。这里的字段包含了类级别变量及实例变量,但是不包括方法内部声明的局部变量。

方法的属性结构

  • JVM预定义了部分attribute,但是编译器自己也可以实现自己的attribute写入class文件里,供运行时使用
  • 不同的attribute通过attribute_name_index来区分

Code结构

code attribute 的作用是保存该方法的结果,如所对应的字节码:

  • 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时,表示处理所有的异常

方法的附加属性:

LineNumberTable:这个属性用来表示code数组中的字节码和Java代码行数之间的关系。这个属性可以用来在调试的时候定位代码执行的行数。

字节码查看工具:

jclasslib地址 https://github.com/ingokegel/jclasslib idea也有插件可以很方便的看

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值