JVM学习笔记(5)之字节码文件剖析

    1. 使用javap -verbose命令分析一个字节码文件时,将会分析该字段的魔数、版本号、常量池、类信息、类的构造方法、类中的方法信息、类变量与成员变量等信息
    1. 魔数:所有的.class字节码文件的前4个字节都是魔数,魔数值为固定值:0xCAFEBABE

看一个简单的java文件MyTest1.java,如下:

package com.zh.bytecode;

/**
 * @author Jack
 * @version 1.0
 * @date 2019/11/5 18:40
 **/
public class MyTest1 {

    private int a = 1;

    public int getA() {
        return a;
    }

    public void setA(int a) {
        this.a = a;
    }
}

执行java -verbose得到的信息如下

D:\jvmstudy\target\classes\com\zh\bytecode>javap -verbose MyTest1.class

Classfile /D:/jvmstudy/target/classes/com/zh/bytecode/MyTest1.class
  Last modified 2019年11月6日; size 477 bytes
  MD5 checksum a2d4352155f8b6902debbf5abc9941fa
  Compiled from "MyTest1.java"
public class com.zh.bytecode.MyTest1
  minor version: 0
  major version: 52
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #3                          // com/zh/bytecode/MyTest1
  super_class: #4                         // java/lang/Object
  interfaces: 0, fields: 1, methods: 3, attributes: 1
Constant pool:
   #1 = Methodref          #4.#20         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#21         // com/zh/bytecode/MyTest1.a:I
   #3 = Class              #22            // com/zh/bytecode/MyTest1
   #4 = Class              #23            // java/lang/Object
   #5 = Utf8               a
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/zh/bytecode/MyTest1;
  #14 = Utf8               getA
  #15 = Utf8               ()I
  #16 = Utf8               setA
  #17 = Utf8               (I)V
  #18 = Utf8               SourceFile
  #19 = Utf8               MyTest1.java
  #20 = NameAndType        #7:#8          // "<init>":()V
  #21 = NameAndType        #5:#6          // a:I
  #22 = Utf8               com/zh/bytecode/MyTest1
  #23 = Utf8               java/lang/Object
{
  public com.zh.bytecode.MyTest1();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: iconst_1
         6: putfield      #2                  // Field a:I
         9: return
      LineNumberTable:
        line 8: 0
        line 10: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  this   Lcom/zh/bytecode/MyTest1;

  public int getA();
    descriptor: ()I
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field a:I
         4: ireturn
      LineNumberTable:
        line 13: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/zh/bytecode/MyTest1;

  public void setA(int);
    descriptor: (I)V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: iload_1
         2: putfield      #2                  // Field a:I
         5: return
      LineNumberTable:
        line 17: 0
        line 18: 5
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       6     0  this   Lcom/zh/bytecode/MyTest1;
            0       6     1     a   I
}
SourceFile: "MyTest1.java"

十六进制查看MyTest1.class文件如下图所示:

在这里插入图片描述

可以看到前4个字节是固定的魔数CA FE BA BE

    1. 魔数之后的4个字节为版本信息,前2个字节表示minor version(次版本号),后2个字节表示major version(主版本号)。这里的次版本号为ff ff,主版本号为00 38,换算成10进制,表示次版本号为65535,主版本号为12,以jdk8为00 34(52),所以56为jdk12,下面在控制台输出java -version来验证是否正确,入图

在这里插入图片描述

    1. 常量池(constant pool):紧接着主版本号后之后的就是常量池入口。一个java类中定义的很多信息都是由常量池来维护和描述的,可以将常量池看作是class文件的资源仓库,比如说java类中定义的方法与变量信息,都是存储在常量池中。常量池中主要存储两类常量:字面量与符号引用。字面量如文本字符串,java中声明为final的常量值等,而符号引用如类和接口的全局限定名,字段的名称和描述符,方法的名称和描述符
    1. 常量池的总结结构:Java类所对应的常量池数量与常量池数组(常量表)这2部分共同构成。常量池数量紧跟在主版本号后面,占据2个字节;常量池数组紧跟在常量池数量之后。常量池数组与一般的数组不同的是,常量池数组中不同的元素的类型、结构都是不同的,长度当然也就不同;但是,每一个元素的第一个数据都是一个u1类型,该节是个标志位,占据1个字节。JVM在解析常量池时,会根据这个u1类型来获取元素的具体类型。值得注意的是,常量池数组中的元素个数=常量池数-1(其中0暂时不使用),目的是满足某些常量池索引值的数据在特定情况下需要表达[不引用任何一个常量池]的含义;根本原因在于,索引为0也是一个常量(保留常量),只不过它不位于常量表中,这个常量就对应null值;所以,常量池的索引从1而非0开始。

这里验证下结论:

  1. 常量池数量:

    版本号后面的2个字节是 00 18,说明常量池的数量为24,然后去查看上面的java -verbose解析出来的class文件信息从1-23,总共23个,常量池数减掉一个保留常量后的数量即23 2)

  2. 常量池数组:
    常量池数量之后的字节是 0a,换算成10进制是10,下面又张表格‘Class文件结构中常量池/表中11种数据类型的结构总表’

在这里插入图片描述

可以看到10是CONSTANT_Methodref_info,下面是2个索引,类型是u2说明占据2个字节,第一个索引代表指向声明方法的类描述符CONSTANT_Class_info的索引项,第二个索引代表指向名称及类型描述符CONSTANT_NameAndType_info的索引项,
第一个索引的字节是00 04,第二个索引的字节是00 14,因此两个索引分别指向第4跟第20个常量元素,可以看到java -verbose解析的class文件信息里显示的也是 #4.#20,进一步验证了我得观点,看verbose解析的信息,常量池元素#4是java/lang/Object,也就是说是Object类,常量池元素#20是NameAndType,它又指向了常量池中#7,#8号元素。#7是是构造函数初始化,#8是构造函数的方法描述()V,是一个空参的构造,返回Void,因为我们类中没有写构造函数,所以系统自动生成了一个空参构造。

接下去看第二个常量元素,第一个字节是09,换算成十进制为9,还是查看11种数据类型的结构总表,9代表CONSTANT_Fieldref_info,下面是2个索引,类型都是U2说明都是占2个字节,第一个索引指向声明字段的类或者接口描述符CONSTANT_Class_info的索引项,第二个索引指向字段描述符CONSTANT_NameAndType_info的索引项,同样第一个索引的字节是00 03(3),第二个索引的字节是00 15(21),常量池中#3号元素又指向#22号元素是 com/zh/bytecode/MyTest1,而常量池中#21号元素又指向#5(a)代表字段name=a,#6(I)代表字段类型type=int类型

    1. 在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;
    1. 对于数组类型来说,每一个维度使用一个前置的[来表示,如int[]被标记为[I,String[][]被标记为[[Ljava/lang/String;
  • 8.用描述符描述方法时,按照先参数列表,后返回值的顺序描述。参数列表按照参数的严格顺序放在一组()之内,如方法:String getRealnameByIdAndNickName(int id,String name)的描述符为:(I,Ljava/lang/String;)Ljava/lang/String;

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值