JVM学习笔记2:Class文件格式

本文深入剖析Java Class文件的结构,详细解读Class文件的组成部分,包括魔数、版本号、常量池、访问标志等关键元素,以及字段、方法和属性表的构成,通过实例分析帮助理解Class文件的组织方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一:class文件概述

     Class文件是JVM的输入,Java虚拟机规范中定义了Class文件的结构。Class文件是JVM实现平台
无关、技术无关的基础。
      1:Class文件是一组以8字节为单位的字节流,各个数据项目按顺序紧凑排列
      2:对于占用空间大于8字节的数据项,按照高位在前的方式分割成多个8字节进行存储
      3:Class文件格式里面只有两种类型:无符号数、表
无符号数:基本数据类型,以u1、u2、u4、u8来代表几个字节的无符号数
表:由多个无符号数和其它表构成的复合数据类型,通常以”_info”结尾


二:ClassFile结构

参看规范4.1 , 同时用 javap –v(verbose)来查看字节码文件

每个class对应一个下图所示的ClassFile结构:

  • magic

     魔数,魔数的唯一作用是确定这个文件是否为一个能被虚拟机所接受的 Class 文件。魔数值固定为 0xCAFEBABE,不会改变。

  • minor_version、major_version
         副版本号和主版本号,minor_version 和 major_version 的值分别表示 Class 文件的副、主版本。它们共同构成了 Class 文件的格式版本号。
  • constant_pool_count
    常量池计数器,constant_pool_count的值等于constant_pool表中的成员数加1。constant_pool 表的索引值只有在大于 0 且小于 constant_pool_count 时才会被
    认为是有效的
     
  • constant_pool[ ]   
        常量池,constant_pool 是一种表结构(这里需要列举一下表就会明白,这个在下面的例子中会有讲解这个结构,返回来在读就会明白),它包含 Class 文件结构及其子结构中引用的所有字符串常量、类或接口名、字段名和其它常量。常量池中的每一项都具备相同的格式特征——第一个字节作为类型标记用于识别该项是哪种类型的常量,称为“tagbyte”。常量池的索引范围是 1 至 constant_pool_count−1。

常量池的项目类型

注意:常量池的常量结构请参看java虚拟机规范4.4章节。

  •  access_flags
    访问标志,access_flags 是一种掩码标志,用于表示某个类或者接口的访问权限及基础属性。access_flags 的取值范围和相应含义见表 4.1 所示。

 带有 ACC_SYNTHETIC 标志的类,意味着它是由编译器自己产生的而不是由程序员编写的源代码生成的。

 带有 ACC_ENUM 标志的类,意味着它或它的父类被声明为枚举类型。

 带有 ACC_INTERFACE 标志的类,意味着它是接口而不是类,反之是类而不是接口。如果一个 Class 文件被设置了 ACC_INTERFACE 标志,那么同时也得设置ACC_ABSTRACT 标志(JLS §9.1.1.1)。同时它不能再设置 ACC_FINAL、ACC_SUPER 和 ACC_ENUM 标志。

 注解类型必定带有 ACC_ANNOTATION 标记,如果设置了 ANNOTATION 标记,ACC_INTERFACE 也必须被同时设置。如果没有同时设置 ACC_INTERFACE 标记,那么这个Class文件可以具有表4.1中的除ACC_ANNOTATION外的所有其它标记。当然 ACC_FINAL 和 ACC_ABSTRACT 这类互斥的标记除外(JLS §8.1.1.2)。

 ACC_SUPER 标志用于确定该 Class 文件里面的 invokespecial 指令使用的是哪一种执行语义。目前 Java 虚拟机的编译器都应当设置这个标志。ACC_SUPER 标记是为了向后兼容旧编译器编译的 Class 文件而存在的,在 JDK1.0.2 版本以前的编译器产生的 Class 文件中,access_flag 里面没有 ACC_SUPER 标志。同时,JDK1.0.2 前的 Java 虚拟机遇到 ACC_SUPER 标记会自动忽略它。

 在表 4.1 中没有使用的 access_flags 标志位是为未来扩充而预留的,这些预留的标志为在编译器中会被设置为 0, Java 虚拟机实现也会自动忽略它们。

  •  this_class
    类索引,this_class 的值必须是对 constant_pool 表中项目的一个有效索引值。constant_pool 表在这个索引处的项必须为 CONSTANT_Class_info 类型常量,表示这个 Class 文件所定义的类或接口。
  • super_class

    父类索引,对于类来说,super_class 的值必须为 0 或者是对 constant_pool 表中项目的一个有效索引值。如果它的值不为 0,那 constant_pool 表在这个索引处的项

    必须为 CONSTANT_Class_info 类型常量(§4.4.1),表示这个 Class 文件所定义的类的直接父类。当前类的直接父类,以及它所有间接父类的 access_flag 中都不能带有 ACC_FINAL 标记。对于接口来说,它的 Class 文件的 super_class 项的值必须是对 constant_pool 表中项目的一个有效索引值。constant_pool 表在这个索引处的
    项必须为代表 java.lang.Object 的 CONSTANT_Class_info 类型常量(§4.4.1)。如果 Class 文件的 super_class 的值为 0,那这个 Class 文件只可能是定义的是
    java.lang.Object 类,只有它是唯一没有父类的类。
     

  • interfaces_count
       接口计数器,interfaces_count 的值表示当前类或接口的直接父接口数量。

  •  interfaces[]
       接口表,interfaces[]数组中的每个成员的值必须是一个对 constant_pool 表中项目的一个有效索引值,它的长度为 interfaces_count。每个成员 interfaces[i] 必须为 CONSTANT_Class_info 类型常量(§4.4.1),其中 0 ≤ i <interfaces_count。在 interfaces[]数组中,成员所表示的接口顺序和对应的源代码中给定的接口顺序(从左至右)一样,即 interfaces[0]对应的是源代码中最左边的接口。

  • fields_count
       字段计数器,fields_count 的值表示当前 Class 文件 fields[]数组的成员个数。fields[]数组中每一项都是一个 field_info 结构(§4.5)的数据项,它用于表示该类或接口声明的类字段或者实例字段 。

  • fields[]
       字段表,fields[]数组中的每个成员都必须是一个 fields_info 结构(§4.5)的数据项,用于表示当前类或接口中某个字段的完整描述。fields[]数组描述当前类或接口
    声明的所有字段,但不包括从父类或父接口继承的部分。

  •  methods_count
    方法计数器,methods_count 的值表示当前 Class 文件 methods[]数组的成员个数。Methods[]数组中每一项都是一个 method_info 结构(§4.5)的数据项。

  • attributes_count
    属性计数器,attributes_count 的值表示当前 Class 文件 attributes 表的成员个数。attributes 表中每一项都是一个 attribute_info 结构(§4.7)的数据项。

  • attributes[]
    属性表,attributes 表的每个项的值必须是 attribute_info 结构(§4.7)。在本规范里,Class 文件结构中的 attributes 表的项包括下列定义的属性:

三:ClassFile实例分析

       1.新建java类代码如下:

public class Main {

    public static void main(String[] args) {
        int a = 1000 ,b = 2000,c =3000 ,d = 4000 ,e = 5000 ,f = 6000;
        int h = a + c + d + e + f/b;
        if(h>1000){
            System.out.println("h>1000");
        }
        StringBuffer stringBuffer = new StringBuffer("JVMTeset");
        int[] y= new int[]{1,2,3,4,5};
        System.out.println(h);
        System.out.println(stringBuffer.toString());
    }
}

2.class文件代码:

3.编译代码得到class文件,javap -v  Main.calss  查看字节码文件。如下:

D:\>javap -v Main.class
Classfile /D:/Main.class
  Last modified 2018-11-8; size 979 bytes
  MD5 checksum 2311f729f8b77e197a691f5cb20f4f03
  Compiled from "Main.java"
public class Main
  SourceFile: "Main.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #11.#39        //  java/lang/Object."<init>":()V
   #2 = Fieldref           #40.#41        //  java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #42            //  h>1000
   #4 = Methodref          #43.#44        //  java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #45            //  java/lang/StringBuffer
   #6 = String             #46            //  JVMTeset
   #7 = Methodref          #5.#47         //  java/lang/StringBuffer."<init>":(Ljava/lang/String;)V
   #8 = Methodref          #43.#48        //  java/io/PrintStream.println:(I)V
   #9 = Methodref          #5.#49         //  java/lang/StringBuffer.toString:()Ljava/lang/String;
  #10 = Class              #50            //  Main
  #11 = Class              #51            //  java/lang/Object
  #12 = Utf8               <init>
  #13 = Utf8               ()V
  #14 = Utf8               Code
  #15 = Utf8               LineNumberTable
  #16 = Utf8               LocalVariableTable
  #17 = Utf8               this
  #18 = Utf8               LMain;
  #19 = Utf8               main
  #20 = Utf8               ([Ljava/lang/String;)V
  #21 = Utf8               args
  #22 = Utf8               [Ljava/lang/String;
  #23 = Utf8               a
  #24 = Utf8               I
  #25 = Utf8               b
  #26 = Utf8               c
  #27 = Utf8               d
  #28 = Utf8               e
  #29 = Utf8               f
  #30 = Utf8               h
  #31 = Utf8               stringBuffer
  #32 = Utf8               Ljava/lang/StringBuffer;
  #33 = Utf8               y
  #34 = Utf8               [I
  #35 = Utf8               StackMapTable
  #36 = Class              #22            //  "[Ljava/lang/String;"
  #37 = Utf8               SourceFile
  #38 = Utf8               Main.java
  #39 = NameAndType        #12:#13        //  "<init>":()V
  #40 = Class              #52            //  java/lang/System
  #41 = NameAndType        #53:#54        //  out:Ljava/io/PrintStream;
  #42 = Utf8               h>1000
  #43 = Class              #55            //  java/io/PrintStream
  #44 = NameAndType        #56:#57        //  println:(Ljava/lang/String;)V
  #45 = Utf8               java/lang/StringBuffer
  #46 = Utf8               JVMTeset
  #47 = NameAndType        #12:#57        //  "<init>":(Ljava/lang/String;)V
  #48 = NameAndType        #56:#58        //  println:(I)V
  #49 = NameAndType        #59:#60        //  toString:()Ljava/lang/String;
  #50 = Utf8               Main
  #51 = Utf8               java/lang/Object
  #52 = Utf8               java/lang/System
  #53 = Utf8               out
  #54 = Utf8               Ljava/io/PrintStream;
  #55 = Utf8               java/io/PrintStream
  #56 = Utf8               println
  #57 = Utf8               (Ljava/lang/String;)V
  #58 = Utf8               (I)V
  #59 = Utf8               toString
  #60 = Utf8               ()Ljava/lang/String;
{
  public Main();
    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 1: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       5     0  this   LMain;

  public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=10, args_size=1 //stack:方法执行时,操作栈的深度
//Locals:局部变量所需的存储空间,单位是slot
//slot是虚拟机为局部变量分配内存所使用的最小单位
//args_size:参数个数,为1的话,因为实例方法默认会传入this,locals也会预留一个
//slot来存放
         0: sipush        1000
         3: istore_1
         4: sipush        2000
         7: istore_2
         8: sipush        3000
        11: istore_3
        12: sipush        4000
        15: istore        4
        17: sipush        5000
        20: istore        5
        22: sipush        6000
        25: istore        6
        27: iload_1
        28: iload_3
        29: iadd
        30: iload         4
        32: iadd
        33: iload         5
        35: iadd
        36: iload         6
        38: iload_2
        39: idiv
        40: iadd
        41: istore        7
        43: iload         7
        45: sipush        1000
        48: if_icmple     59
        51: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        54: ldc           #3                  // String h>1000
        56: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        59: new           #5                  // class java/lang/StringBuffer
        62: dup
        63: ldc           #6                  // String JVMTeset
        65: invokespecial #7                  // Method java/lang/StringBuffer."<init>":(Ljava/lang/String;)V
        68: astore        8
        70: iconst_5
        71: newarray       int
        73: dup
        74: iconst_0
        75: bipush        11
        77: iastore
        78: dup
        79: iconst_1
        80: bipush        22
        82: iastore
        83: dup
        84: iconst_2
        85: bipush        33
        87: iastore
        88: dup
        89: iconst_3
        90: bipush        44
        92: iastore
        93: dup
        94: iconst_4
        95: bipush        55
        97: iastore
        98: astore        9
       100: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       103: iload         7
       105: invokevirtual #8                  // Method java/io/PrintStream.println:(I)V
       108: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       111: aload         8
       113: invokevirtual #9                  // Method java/lang/StringBuffer.toString:()Ljava/lang/String;
       116: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       119: return
      LineNumberTable:
        line 4: 0
        line 5: 27
        line 6: 43
        line 7: 51
        line 9: 59
        line 10: 70
        line 11: 100
        line 12: 108
        line 13: 119
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0     120     0  args   [Ljava/lang/String;
               4     116     1     a   I
               8     112     2     b   I
              12     108     3     c   I
              17     103     4     d   I
              22      98     5     e   I
              27      93     6     f   I
              43      77     7     h   I
              70      50     8 stringBuffer   Ljava/lang/StringBuffer;
             100      20     9     y   [I
      StackMapTable: number_of_entries = 1
           frame_type = 255 /* full_frame */
          offset_delta = 59
          locals = [ class "[Ljava/lang/String;", int, int, int, int, int, int, int ]
          stack = []

以上是编译后的class文件和字节码文件信息,具体内容请参照 java虚拟机规范

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值