栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

class文件(jdk8)结构

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

class文件(jdk8)结构

一个class文件由一个单一的classfile构成
ClassFile {
    u4             magic;
    u2             minor_version;
    u2             major_version;
    u2             constant_pool_count;
    cp_info        constant_pool[constant_pool_count-1];
    u2             access_flags;
    u2             this_class;
    u2             super_class;
    u2             interfaces_count;
    u2             interfaces[interfaces_count];
    u2             fields_count;
    field_info     fields[fields_count];
    u2             methods_count;
    method_info    methods[methods_count];
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

以下面这段代码作为示例,分析class文件结构

package org.fenixsoft.clazz;

public class TestClass {
    private int m;
    public int inc() {
        return m + 1;
    }

}

 使用winhex打开编译后的class文件,内容如下

 

每个Class文件的头4个字节被称为魔数(Magic Number),它的唯一作用是确定这个文件是否为
一个能被虚拟机接受的Class文件, 值为 0xCAFEBABE

魔数之后的四个字节为版本号,第5和第6个字节是次版本号(MinorVersion),第7和第8个字节是主版本号(Major Version)。我的jdk是1.8.0_73 , 次版本号为ox0000(从jdk1.2开始,次版本号固定位0), 主版本号为ox0034,转换为10进制就是52

版本号后面是常量池, 首先是常量池容量计数值, 这个容量计数是从1而不是0开始的,如下图所示,常量池容量(偏移地址:0x00000008)为十六进制数0x0016,即十进制的22,这就
代表常量池中有21项常量,索引值范围为1~21

常量池表格式如下

cp_info {
    u1 tag;
    u1 info[];
}

 常量池的项目类型, 17种常量类型各自有着完全独立的数据结构

Constant TypeValue
CONSTANT_Class7
CONSTANT_Fieldref9
CONSTANT_Methodref10
CONSTANT_InterfaceMethodref11
CONSTANT_String8
CONSTANT_Integer3
CONSTANT_Float4
CONSTANT_Long5
CONSTANT_Double6
CONSTANT_NameAndType12
CONSTANT_Utf81
CONSTANT_MethodHandle15
CONSTANT_MethodType16
CONSTANT_InvokeDynamic18

 常量池的第一项常量 它的标志位是ox0A,查表可知这个常量属于CONSTANT_Methodref

CONSTANT_Methodref结构如下

CONSTANT_Methodref_info {
    u1 tag;
    u2 class_index;
    u2 name_and_type_index;
}

tag是标志位,为10

class_index 指向声明方法的类标识符CONSTANT_Methodref的索引项,此处为ox0004

name_and_type_index 是指向名称及类型标识符 CONSTANT_NameAndType的索引项,此处为ox0012

对class文件使用 javap 可以看到所有的常量

javap -verbose TestClass
警告: 二进制文件TestClass包含org.fenixsoft.clazz.TestClass
Classfile /D:/thkj/spring/out/production/spring/org/fenixsoft/clazz/TestClass.class
  Last modified 2021-12-14; size 393 bytes
  MD5 checksum ca6d8b2868083171147fd4edb61a731b
  Compiled from "TestClass.java"
public class org.fenixsoft.clazz.TestClass
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#18         // java/lang/Object."":()V
   #2 = Fieldref           #3.#19         // org/fenixsoft/clazz/TestClass.m:I
   #3 = Class              #20            // org/fenixsoft/clazz/TestClass
   #4 = Class              #21            // java/lang/Object
   #5 = Utf8               m
   #6 = Utf8               I
   #7 = Utf8               
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lorg/fenixsoft/clazz/TestClass;
  #14 = Utf8               inc
  #15 = Utf8               ()I
  #16 = Utf8               SourceFile
  #17 = Utf8               TestClass.java
  #18 = NameAndType        #7:#8          // "":()V
  #19 = NameAndType        #5:#6          // m:I
  #20 = Utf8               org/fenixsoft/clazz/TestClass
  #21 = Utf8               java/lang/Object
{
  public org.fenixsoft.clazz.TestClass();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lorg/fenixsoft/clazz/TestClass;

  public int inc();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field m:I
         4: iconst_1
         5: iadd
         6: ireturn
      LineNumberTable:
        line 6: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       7     0  this   Lorg/fenixsoft/clazz/TestClass;
}
SourceFile: "TestClass.java"

在常量池结束之后,紧接着的2个字节代表访问标志(access_flags)

 TestClass是一个普通Java类,不是接口、枚举、注解或者模
块,被public关键字修饰但没有被声明为final和abstract,并且它使用了JDK 1.2之后的编译器进行编译,因此它的ACC_PUBLIC、ACC_SUPER标志应当为真,而ACC_FINAL、ACC_INTERFACE、ACC_ABSTRACT、ACC_SYNTHETIC、ACC_ANNOTATION、ACC_ENUM、ACC_MODULE这七个标志应当为假,因此它的access_flags的值应为:0x0001|0x0020=0x0021。从下图看到,access_flags标志(偏移地址:0x000000EF)的确为0x0021

类索引、父类索引和接口索引集合都按顺序排列在访问标志之后

 类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引集合
(interfaces)是一组u2类型的数据的集合

类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名

此例中.类索引为ox0003 ,对应为org/fenixsoft/clazz/TestClass 父类索引ox0004, 对应于java/lang/Object接口索引为0

接口索引之后是字段的数量,为ox0001, 表示有一个字段

字段表(field_info)用于描述接口或者类中声明的变量

字段修饰符放在access_flags项目中

 此例为ox0002, 即ACC_PRIVATE,即private

 跟随access_flags标志的是两项索引值:name_index和descriptor_index。它们都是对常量池项的引用,分别代表着字段的简单名称以及字段和方法的描述符

name_index为 ox0005 ,即为常量池中的m

标志描述符

 

descriptor_index为ox0006 即为常量池中的I, 对应int类型

在descriptor_index之后跟随着一个属性表集合,用于存储一些额外的信息,字段表可以在属性表中附加描述零至多项的额外信息。对于本例中的字段m,它的属性表计数器为0,也就是没有需要额外描述的信息

字段表之后是方法表

 

方法表结构如下

 

第一个u2类型的数据(即计数器容量)的值为0x0002,代表集合中有两个方法,这两个方法为编译器添加的实例构造器和源码中定义的方法inc()。第一个方法的访问标志值为0x0001,也就是只有ACC_PUBLIC标志为真,名称索引值为0x0007,对应常量池得方法名为“”,描述符索引值为0x0008,对应常量为“()V”,属性表计数器attributes_count的值为0x0001,表示此方法的属性表集合有1项属性,属性名称的索引值为0x0009,对应常量为“Code”,说明此属性是方法的字节码描述

之后是属性表

属性表结构如下

 属性表长度为ox002f, 即47

Code属性表

Java程序方法体里面的代码经过Javac编译器处理之后,最终变为字节码指令存储在Code属性内

 

 

虚拟机读取到字节码区域的长度后,按照顺序依次读入紧随的5个字节,并根据字节码指令表翻译出所对应的字节码指令。翻译“2A B7000A B1”的过程为

1)读入2A,查表得0x2A对应的指令为aload_0,这个指令的含义是将第0个变量槽中为reference类型的本地变量推送到操作数栈顶。
2)读入B7,查表得0xB7对应的指令为invokespecial,这条指令的作用是以栈顶的reference类型的数据所指向的对象作为方法接收者,调用此对象的实例构造器方法、private方法或者它的父类的方法。这个方法有一个u2类型的参数说明具体调用哪一个方法,它指向常量池中的一个CONSTANT_Methodref_info类型常量,即此方法的符号引用。
3)读入000A,这是invokespecial指令的参数,代表一个符号引用,查常量池得0x000A对应的常量为实例构造器“()”方法的符号引用。
4)读入B1,查表得0xB1对应的指令为return,含义是从方法的返回,并且返回值为void。这条指
令执行后,当前方法正常结束。

查看javap 结果的字节码指令

{
  public org.fenixsoft.clazz.TestClass();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lorg/fenixsoft/clazz/TestClass;

  public int inc();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field m:I
         4: iconst_1
         5: iadd
         6: ireturn
      LineNumberTable:
        line 6: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       7     0  this   Lorg/fenixsoft/clazz/TestClass;
}

这个类有两个方法——实例构造器()和inc(),这两个方法很明显都是没有参数的,为什么Args_size会为1?而且无论是在参数列表里还是方法体内,都没有定义任何局部变量,那Locals又为什么会等于1?如果有这样疑问的读者,大概是忽略了一条Java语言里面的潜规则:在任何实例方法里面,都可以通过“this”关键字访问到此方法所属的对象。这个访问机制对Java程序的编写很重要,而它的实现非常简单,仅仅是通过在Javac编译器编译的时候把对this关键字的访问转变为对一个普通方法参数的访问,然后在虚拟机调用实例方法时自动传入此参数而已。因此在实例方法的局部变量表中至少会存在一个指向当前对象实例的局部变量,局部变量表中也会预留出第一个变量槽位来存放对象实例的引用,所以实例方法参数值从1开始计算。这个处理只对实例方法有效,如果代码清单6-1中的inc()方法被声明为static,那Args_size就不会等于1而是等于0了。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/667310.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号