Java字节码文件理解
首先写一个简单的java类
package com.jvm.bytecode;
/**
* @Author 晨边#CB
* @Date:created in 2019/12/3 10:16
* @Version V1.0
**/
public class MyTest1 {
private int a = 1;
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
}
会在target目录下面自动生成一个自己的构造类
并且在命令函运行 javap XXX 反编译代码如下
运行javap -c XXX代码如下
也可以运行java -verboase XXX查看更详细的信息
对应的十六位进制.class
说明:
- 使用javap -verbose 命令分析一个字节码文件时,将会分析该字节码文件的魔数,版本号,常量池,类信息,类的构造方法,类中的方法信息,类变量与陈冠变量等信息。
- 魔数:所有的.class字节码的文件的前4个字节都是魔数,魔数值固定为:0xCAFEBABE。(咖啡宝贝)
- 魔数之后的4个字节为版本信息,前两个字节表示minor version(次版本号) 后两个字节表示major version(主版本号) 。 这里的版本号为 00 00 00 34,换算成10进制表示次版本号为0,而主版本号为52.->jdk8。所以该文件的版本号为1.8.0.可以通过java -version命令来验证这一点。
- 常量池(constant pool):紧接着主版本号的就是常量池入口。一个Java类定义的很多信息都是由常量池来维护和描述的。可以将常量池看作是Class文件的资源仓库,比如说java类定义的方法与变量信息,都是存储在常量池中。常量池主要存储两类常量:字面量与符号引用。字面量如文本字符串,Java中声明为final的常量值等。,而符号引用如类的接口和全局限定名。字段的名称和描述符方法的名称和描述符等。
- 常量池的总体结构:Java类所对应的常量池主要由常量池数量与常量池数组这两部分共同构成。常量池数量紧跟在主版本号后面,占据2个字节;常量池数组则紧跟在常量池数量之后。常量池数组与一般的数组不同的是,常量池数组中不同的元素的类型结构都是不同的,长度当然也就不同;但是每一种元素的第一个数组都是u1类型,该字节是一个标志位,占据1个字节。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,Sring [][]被记录为[[L/java/lang/String;
8. 用描述符来描述方法时,按照先参数列表,后返回值的顺序来描述。参数列表按照参数的严格顺序放在一组()之内,如方法:String getRealnamebyIdAndNickname(int id ,String name)的描述符为:(I,Ljava/lang/String;) Ljava/lang/String;
Access_Flag标志位:(表示方法)
Code结构
注:每个函数都会默认在函数的参数里面传入一个this
推荐使用idea工具:jclasslib
查看关于字节码有关的信息。