一个普通的Java类与其字节码举例:

字节码中包含哪些内容?
魔数(0xCAFEBABE)、版本号、常量池、访问标志、一系列索引(当前类、父类、接口)、字段表和方法表、附加属性
一般用什么指令来看字节码?
javap -verbose 类名
- 版本号
魔数后的4个字节,前俩为次版本号、后俩为主版本号。拿到主版本号要去Oracle官方查询才知道真正的版本号(1.8、1.7...)
- 常量池(很重要)
常量池的分类
字面量指一些基本类型(比如CONSTANT_Integer_info),没有其它特殊含义。除此之外也有其它文献指出,字面量就是final修饰的常量值。 符号引用则是类、接口这些的全局限定名、还有字段名称和描述符、方法名称和描述符。
符号引用中的标记位与索引位举例:标记位0A表示tag=10,可以从常量池中找到如下信息,从图中找到索引位如u2 class_index意为:tag这个标志位往后2个字节,是类信息的常量池索引。

常量池整体又分为两部分:
常量池计数器(计存了多少常量,需要减1,因为常量池中第0号常量被jvm占用了,表示什么都不引用) 常量池数据区(比较复杂,有十几种类型,要查表。可以理解为一个资源仓库)
- 访问标志
描述是接口还是普通类、是否被Public、Final等修饰符修饰。
- 当前类名
描述当前类全限定名。实际上保存的是常量池的索引,真正的值在常量池中
- 父类名称
描述父类全限定名,同上
- 接口信息
描述该类/父类实现的接口数量,以及名称对应的常量值中的索引值
- 字段表
类和接口中声明的静态变量、实例变量,也是两部分:字段个数以及详细信息
fields_info:访问修饰符、字段名称索引、字段描述(也就是字段名)
- 方法表
紧跟在字段表后面,也是两部分:方法个数和方法详细信息(访问标志、方法名)
方法的属性部分比较复杂,主要分为几块:
1、“Code区”(最重要的部分):源代码对应的JVM指令操作码,在进行字节码增强时重点操作的就是“Code区”这一部分。
2、“LineNumberTable”:行号表,将Code区的操作码和源代码中的行号对应,Debug时会起到作用(源代码走一行,需要走多少个JVM指令操作码)。
3、“LocalVariableTable”:本地变量表,包含This和局部变量,之所以可以在每一个方法内部都可以调用This,是因为JVM将This作为每一个方法的第一个参数隐式进行传入。当然,这是针对非Static方法而言。

- 附加属性表
描述字段的一些附加属性,比如trasient、volatile关键字等等
最后记录一些字节码中常见的字符含义:
()V 表示无参无返回
<init> 表示构造函数
FAQ
1、为何可以用this来调用?
因为java编译器默认给每个实例方法添加一个入参参数,而且是第一个参数:this对象。
2、为何能够从报错信息中定位到是哪一行代码?
因为字节码有一个LineNumberTable - 行号表来记录源代码中的行号和字节码中Code区的行号码的映射。
3、字节码为何要这样设计和排序?
保证jvm能识别的情况下,把文件压缩得足够小(被类加载器加载到方法区时候越小越好!)
参考链接:字节码增强技术探索
网友评论