带你分析.class文件

.class文件

引言

相信作为拥有多年开发经验的你来说,对于.class文件并不陌生,但是你真的认真分析过他里面的规则和逻辑吗?今天就带着你来看看.class文件到底是什么个东西.

1. 初体验

我们可以用javap -v 将.class文件编译成更为可读的助记符,例如:
(请忽略我的文件目录,没啥关系跟这个代码)

javap -v xxx.class

D:\learn\springboot-01-cache\target\classes\com\chy\cache\java>javap -v ByteCode.class
Classfile /D:/learn/springboot-01-cache/target/classes/com/chy/cache/java/ByteCode.class
  Last modified 2020-6-22; size 579 bytes
  MD5 checksum 984bc023b1033c3fa24dc812ed1c12e6
  Compiled from "ByteCode.java"
public class com.chy.cache.java.ByteCode
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#21         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#22         // com/chy/cache/java/ByteCode.userName:Ljava/lang/String;
   #3 = Class              #23            // com/chy/cache/java/ByteCode
   #4 = Class              #24            // java/lang/Object
   #5 = Utf8               userName
   #6 = Utf8               Ljava/lang/String;
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/chy/cache/java/ByteCode;
  #14 = Utf8               getUserName
  #15 = Utf8               ()Ljava/lang/String;
  #16 = Utf8               setUserName
  #17 = Utf8               (Ljava/lang/String;)V
  #18 = Utf8               MethodParameters
  #19 = Utf8               SourceFile
  #20 = Utf8               ByteCode.java
  #21 = NameAndType        #7:#8          // "<init>":()V
  #22 = NameAndType        #5:#6          // userName:Ljava/lang/String;
  #23 = Utf8               com/chy/cache/java/ByteCode
  #24 = Utf8               java/lang/Object
{
  public com.chy.cache.java.ByteCode();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 4: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/chy/cache/java/ByteCode;

  public java.lang.String getUserName();
    descriptor: ()Ljava/lang/String;
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field userName:Ljava/lang/String;
         4: areturn
      LineNumberTable:
        line 10: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/chy/cache/java/ByteCode;

  public void setUserName(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: putfield      #2                  // Field userName:Ljava/lang/String;
         5: return
      LineNumberTable:
        line 14: 0
        line 15: 5
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       6     0  this   Lcom/chy/cache/java/ByteCode;
            0       6     1 userName   Ljava/lang/String;
    MethodParameters:
      Name                           Flags
      userName
}
SourceFile: "ByteCode.java"

这个就是我们更加熟悉的jvm助记符,但是.class文件还有他的另一张面孔,那就是16进制的文件:

CA FE BA BE 00 00 00 34  00 19 0A 00 04 00 15 09
00 03 00 16 07 00 17 07  00 18 01 00 08 75 73 65
72 4E 61 6D 65 01 00 12  4C 6A 61 76 61 2F 6C 61
6E 67 2F 53 74 72 69 6E  67 3B 01 00 06 3C 69 6E
69 74 3E 01 00 03 28 29  56 01 00 04 43 6F 64 65
01 00 0F 4C 69 6E 65 4E  75 6D 62 65 72 54 61 62
6C 65 01 00 12 4C 6F 63  61 6C 56 61 72 69 61 62
6C 65 54 61 62 6C 65 01  00 04 74 68 69 73 01 00
23 4C 63 6F 6D 2F 63 68  79 2F 63 61 63 68 65 2F
6A 61 76 61 2F 54 75 6C  69 6E 67 42 79 74 65 43
6F 64 65 3B 01 00 0B 67  65 74 55 73 65 72 4E 61
6D 65 01 00 14 28 29 4C  6A 61 76 61 2F 6C 61 6E
67 2F 53 74 72 69 6E 67  3B 01 00 0B 73 65 74 55
73 65 72 4E 61 6D 65 01  00 15 28 4C 6A 61 76 61
2F 6C 61 6E 67 2F 53 74  72 69 6E 67 3B 29 56 01
00 10 4D 65 74 68 6F 64  50 61 72 61 6D 65 74 65
72 73 01 00 0A 53 6F 75  72 63 65 46 69 6C 65 01
00 13 54 75 6C 69 6E 67  42 79 74 65 43 6F 64 65
2E 6A 61 76 61 0C 00 07  00 08 0C 00 05 00 06 01
00 21 63 6F 6D 2F 63 68  79 2F 63 61 63 68 65 2F
6A 61 76 61 2F 54 75 6C  69 6E 67 42 79 74 65 43
6F 64 65 01 00 10 6A 61  76 61 2F 6C 61 6E 67 2F
4F 62 6A 65 63 74 00 21  00 03 00 04 00 00 00 01
00 02 00 05 00 06 00 00  00 03 00 01 00 07 00 08
00 01 00 09 00 00 00 2F  00 01 00 01 00 00 00 05
2A B7 00 01 B1 00 00 00  02 00 0A 00 00 00 06 00
01 00 00 00 06 00 0B 00  00 00 0C 00 01 00 00 00
05 00 0C 00 0D 00 00 00  01 00 0E 00 0F 00 01 00
09 00 00 00 2F 00 01 00  01 00 00 00 05 2A B4 00
02 B0 00 00 00 02 00 0A  00 00 00 06 00 01 00 00
00 0C 00 0B 00 00 00 0C  00 01 00 00 00 05 00 0C
00 0D 00 00 00 01 00 10  00 11 00 02 00 09 00 00
00 3E 00 02 00 02 00 00  00 06 2A 2B B5 00 02 B1
00 00 00 02 00 0A 00 00  00 0A 00 02 00 00 00 10
00 05 00 11 00 0B 00 00  00 16 00 02 00 00 00 06
00 0C 00 0D 00 00 00 00  00 06 00 05 00 06 00 01
00 12 00 00 00 05 01 00  05 00 00 00 01 00 13 00
00 00 02 00 14 

通过editplus直接打开的.class文件
这个16进制的.class文件和咱们的jvm助记符的每一行指令都是一一对应的,那么他们有什么规律呢?

2.逐行翻译.class文件

开篇提到了.class文件其实是16进制表示的,那么一个16进制的数字,例如C,他其实是4bit,
那么就可推断出,CA,其实是一个Byte,因为1Byte = 8 bit.
所以.class文件开头的CA FA BA BE一共占了4个字节,那么他代表什么含义呢?
其实他叫Magic Number(魔数),他是一个固定值,他也相当于一个Logo,也是一个标识.
紧接着后面的00 00 代表了次版本号,也就是此版本是0,占了两个字节.
00 34是主版本号,仍然占两个字节,00 34 转换成10进制是52,而52对应的jdk版本就是1.8,可以推出,51是jdk几呢,没错就是1.7!

在这里插入图片描述

00 19这个代表了常量池常量的个数,也就是25个,
在这里插入图片描述
这个怎么只有24个呢,其实第一位是null.被jvm占用了.
接下来的所有16进制数值,就代表了常量池里的每行代码的含义了.
0A 代表了常量池的标记位,A = 10,他是代表着这个一个描述方法的常量,method_info类型
在这里插入图片描述
这五个字节代表了描述方法的常量.
在这里插入图片描述

u1表示一个字节是10,u2表示两个字节的索引位,也就是常量池的索引,根据.class文件可以看出索引位是00 04,表示的是哪个类
在这里插入图片描述
00 15指向了21,表示了名称和类型.
在这里插入图片描述
也就是Object类的构造方法,返回类型是void.
紧接着是09,
在这里插入图片描述
第二个常量他表示了一个字段的信息,前一位是标记位,后一位索引位,最后一位表示了名字和类型,占了5个字节
在这里插入图片描述
在这里插入图片描述
03,指向了3号位置,16指向了22号位置,
在这里插入图片描述
ByteCode类的userName方法名,返回值为String.
第三个常量为,
在这里插入图片描述
在这里插入图片描述
07标志位,表示了class信息,占了三个字节,u2是他的索引位.
在这里插入图片描述
17,是23指向了ByteCode
在这里插入图片描述
第四个常量,又是07
在这里插入图片描述
跟上面是一样的,占三个字节,找到18 = 24的索引位置,它的父类Object
在这里插入图片描述
第五个常量,01标记位,表示了字面量utf8结果体,u2两个字节表示字符长度08,也就是表示是8个长度,紧接着往后面数8个长度位
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
75 73 65 72 4E 61 6D 65
在这里插入图片描述
在这里插入图片描述
第六个常量,又是01,字面量,12是长度位,往后面数18个字节,表示String类型
在这里插入图片描述
在这里插入图片描述
不知道找没找到点感觉,加快速度,第七个常量,紧接着后面又是01,这个是标志位,跟上面同理,01后面的00 06是长度为,向后数6个字节.
在这里插入图片描述
在这里插入图片描述
第八个常量又是01,后面00 03长度位,数三个字节,在这里插入图片描述
在这里插入图片描述
第九个常量,又是01,吐了,在这里插入图片描述后面长度是04,数四个字节,
在这里插入图片描述
他代表字节码表.
第十个常量,又是01,00 0F 数15个字节,
在这里插入图片描述
在这里插入图片描述
代表行号表,这里有个点就是,大家有没有想过,为什么抛异常的时候,他可以知道,是哪行抛出的异常.其实这里的行号表,和我们的java源码是一一对应的.
第十一个常量又是01和上面一样,数18位,
在这里插入图片描述
在这里插入图片描述代表局部变量表(本地变量表),每个方法都有一个LocalVariableTable,
在这里插入图片描述
stack=1是栈的深度,locals=1是局部变量,也就是说局部变量表在编译时期就确定了.
从截图可以看到ByteCode()是他的构造方法,但是从图上看,他就一个局部变量.其实是this,他在编译时会作为隐式的参数放在局部变量表的第一个位置.
第十二个常量
在这里插入图片描述
在这里插入图片描述
第十三个常量,
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
他描述的是一个全限定名.
在这里插入图片描述
在这里插入图片描述

相信大家都会了吧.
常量池分析完了,接着分析下面的访问修饰符.
在这里插入图片描述
在这里插入图片描述
00 21,
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
紧接着下面就是this class name
在这里插入图片描述
在这里插入图片描述
00 03指向了常量池3号位置
在这里插入图片描述
常量池是不是很有用,他就相当于是一个资源池,可以重复的引用.
下面是父类的名称,
在这里插入图片描述
00 04 ,指向了常量池的第四位
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
表示实现了0个接口,那么有的小伙伴可能会问了,那么这个接口你这么说也会有上限啊?答案是肯定的 FFFF就是他的上限,这个是个细节哦嘻嘻嘻.
接下来是字段的个数
00 01
,有一个字段. userName.接下来是字段表
00 02表示字段表中的类型
在这里插入图片描述
在这里插入图片描述
**00 05 在这里插入图片描述
接下来是描述符的索引
00 06 **
在这里插入图片描述
接下来是属性表的个数.**00 00 **表示没有.
**00 03 **表示方法的个数,在这里插入图片描述
get set外加构造.
紧接着后面又是方法的方发表
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
“name_index”: “u2(00 07)->desc:表示方法名称的索引指向常量池第七个位置#7 表示 再编译时期生成的默认的构造函数”,
“descciptor_index”: “u2(00 08)->desc:表示方法描述索引指向常量池#8 表示:()V 无参数,无返回值”,
“attribute_count”: “u2(00 01)->desc:表示方法的属性个数”,表示只有一个

    "Code": {
      "attribute_name_index": "u2(00 09)->desc:我们属性的名称指向常量值索引的#9 位置 值为Code",
      "attribute_length": "u4(00 00 00 2F)-desc:表示我们的Code属性紧接着下来的47个字节是Code的内容",
      "max_stack": "u2(00 01)->desc:表示该方法的最大操作数栈的深度1",
      "max_locals": "u2(00 01)->desc:表示该方法的局部变量表的个数为1",
      "Code_length": "u4(00 00 00 05)->desc:指令码的长度为5",
      "Code[Code_length]": "2A B7 00 01 B1 其中0x002A->对应的字节码注记符是aload_0;0xB7->invokespecial;00 01表示表示是B7指令码操作的对象指向常量池中的#1(java/lang/Object.<init>:()V);B1表示调用方法返回void",
      "exception_table_length": "u2(00 00)->表示该方法不抛出异常,故exception_info没有异常信息",
      "exception_info": {},
      "attribute_count": "u2(00 02)->desc表示code属性表的属性个数为2",
      "attribute_info": {
        "LineNumberTable": {
          "attribute_name_index": "u2(00 0A)当前属性表名称的索引指向我们的常量池#10(LineNumberTable)",
          "attribute_length": "u4(00 00 00 06)当前属性表属性的字段占用6个字节是用来描述line_number_info",
          "mapping_count": "u2(00 01)->desc:该方法指向的指令码和源码映射的对数  表示一对",
          "line_number_infos": {
            "line_number_info[0]": {
              "start_pc": "u2(00 00)->desc:表示指令码的行数",
              "line_number": "u2(00 06)->desc:源码的行号"
            }
          }
        },
        "localVariableTable": {
          "attribute_name_index": "u2(00 0B)当前属性表名称的索引指向我们的常量池#10(localVariableTable)",
          "attribute_length": "u4(00  00 00 0C)当前属性表属性的字段占用12个字节用来描述local_variable_info",
          "local_variable_length": "u2(00 01)->desc:表示局部变量的个数1个",
          "local_variable_infos": {
            "local_variable_info[0]": {
              "start_pc": "u2(00 00 )->desc:这个局部变量的生命周期开始的字节码偏移量",
              "length:": "u2(00 05)->作用范围覆盖的长度为5",
              "name_index": "u2(00 0c)->字段的名称索引指向常量池12的位置 this",
              "desc_index": "u2(00 0D)局部变量的描述符号索引->指向#13的位置Lcom/tuling/smlz/jvm/classbyatecode/TulingByteCode;",
              "index": "u2(00 00)->desc:index是这个局部变量在栈帧局部变量表中Slot的位置"
            }
          }
        }
      }
    }

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值