class文件1-文件格式

2 篇文章 0 订阅
1 篇文章 0 订阅

在讨论JVM的相关知识之前,先学习一下class文件。

class File

每个class文件是由8位字节的流组成,其数据类型有:u1 u2 u4和_info,基本结构如下:

ClassFile { 
    u4 magic;  // 魔法数字,表明当前文件是.class文件,固定0xCAFEBABE
    u2 minor_version; // 为Class文件的副版本,现在基本固定为0x0000
    u2 major_version; // 为Class文件的主版本,对应jdk发行版本
    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]; // 各种属性
}
  • u1 1个字节 readUnsignedByte
  • u2 2个字节 readUnsignedShort
  • u4 4个字节 readInt
  • _info 表类型(hotspot中的写法),是多个无符号数(即u1 u2 u4)或者其他表组合而成的复合数据,如:cp_info、method_info等

通过官方文档查看对应版本的class文件格式,主要内容在第4章(The class File Format),本文内容也主要来自该文档

通常编译后的class文件是一个16进制文件,本文以HelloWorld.java文件为例来描述

1字节=8byte,对16进制文件,一个字符可以通过4个byte表示,因此1字节对应2个16进制字符
0xCAFEBABE 对应4个字节,即u4

public class HelloWorld {
    public static void main(String[] args) {
        String hello = "Hello World";
        System.out.println(hello);
    }
}

本文演示的java版本是jdk13.0.2(对应的版本号是57,即0x0039),编译后的class文件如下:

cafe babe 0000 0039 0024 0a00 0200 0307
0004 0c00 0500 0601 0010 6a61 7661 2f6c
616e 672f 4f62 6a65 6374 0100 063c 696e
6974 3e01 0003 2829 5608 0008 0100 0b48
656c 6c6f 2057 6f72 6c64 0900 0a00 0b07
000c 0c00 0d00 0e01 0010 6a61 7661 2f6c
616e 672f 5379 7374 656d 0100 036f 7574
0100 154c 6a61 7661 2f69 6f2f 5072 696e
7453 7472 6561 6d3b 0a00 1000 1107 0012
0c00 1300 1401 0013 6a61 7661 2f69 6f2f
5072 696e 7453 7472 6561 6d01 0007 7072
696e 746c 6e01 0015 284c 6a61 7661 2f6c
616e 672f 5374 7269 6e67 3b29 5607 0016
0100 1b70 7269 2f6c 6a77 2f6a 6176 612f
6a76 6d2f 4865 6c6c 6f57 6f72 6c64 0100
0443 6f64 6501 000f 4c69 6e65 4e75 6d62
6572 5461 626c 6501 0012 4c6f 6361 6c56
6172 6961 626c 6554 6162 6c65 0100 0474
6869 7301 001d 4c70 7269 2f6c 6a77 2f6a
6176 612f 6a76 6d2f 4865 6c6c 6f57 6f72
6c64 3b01 0004 6d61 696e 0100 1628 5b4c
6a61 7661 2f6c 616e 672f 5374 7269 6e67
3b29 5601 0004 6172 6773 0100 135b 4c6a
6176 612f 6c61 6e67 2f53 7472 696e 673b
0100 0568 656c 6c6f 0100 124c 6a61 7661
2f6c 616e 672f 5374 7269 6e67 3b01 000a
536f 7572 6365 4669 6c65 0100 0f48 656c
6c6f 576f 726c 642e 6a61 7661 0021 0015
0002 0000 0000 0002 0001 0005 0006 0001
0017 0000 002f 0001 0001 0000 0005 2ab7
0001 b100 0000 0200 1800 0000 0600 0100
0000 0900 1900 0000 0c00 0100 0000 0500
1a00 1b00 0000 0900 1c00 1d00 0100 1700
0000 4700 0200 0200 0000 0b12 074c b200
092b b600 0fb1 0000 0002 0018 0000 000e
0003 0000 000c 0003 000d 000a 000e 0019
0000 0016 0002 0000 000b 001e 001f 0000
0003 0008 0020 0021 0001 0001 0022 0000
0002 0023 

class文件解析有多种查看方式,最简单的就是通过javap命令查看

javap -v HelloWorld

主要结果如下

public class pri.ljw.java.jvm.HelloWorld
  minor version: 0
  major version: 57
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #21                         // pri/ljw/java/jvm/HelloWorld
  super_class: #2                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #2.#3          // java/lang/Object."<init>":()V
   #2 = Class              #4             // java/lang/Object
   #3 = NameAndType        #5:#6          // "<init>":()V
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = String             #8             // Hello World
   #8 = Utf8               Hello World
   #9 = Fieldref           #10.#11        // java/lang/System.out:Ljava/io/PrintStream;
  #10 = Class              #12            // java/lang/System
  #11 = NameAndType        #13:#14        // out:Ljava/io/PrintStream;
  #12 = Utf8               java/lang/System
  #13 = Utf8               out
  #14 = Utf8               Ljava/io/PrintStream;
  #15 = Methodref          #16.#17        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #16 = Class              #18            // java/io/PrintStream
  #17 = NameAndType        #19:#20        // println:(Ljava/lang/String;)V
  #18 = Utf8               java/io/PrintStream
  #19 = Utf8               println
  #20 = Utf8               (Ljava/lang/String;)V
  #21 = Class              #22            // pri/ljw/java/jvm/HelloWorld
  #22 = Utf8               pri/ljw/java/jvm/HelloWorld
  #23 = Utf8               Code
  #24 = Utf8               LineNumberTable
  #25 = Utf8               LocalVariableTable
  #26 = Utf8               this
  #27 = Utf8               Lpri/ljw/java/jvm/HelloWorld;
  #28 = Utf8               main
  #29 = Utf8               ([Ljava/lang/String;)V
  #30 = Utf8               args
  #31 = Utf8               [Ljava/lang/String;
  #32 = Utf8               hello
  #33 = Utf8               Ljava/lang/String;
  #34 = Utf8               SourceFile
  #35 = Utf8               HelloWorld.java
{
  public pri.ljw.java.jvm.HelloWorld();
    descriptor: ()V
    flags: (0x0001) 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 9: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lpri/ljw/java/jvm/HelloWorld;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: ldc           #7                  // String Hello World
         2: astore_1
         3: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
         6: aload_1
         7: invokevirtual #15                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        10: return
      LineNumberTable:
        line 12: 0
        line 13: 3
        line 14: 10
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      11     0  args   [Ljava/lang/String;
            3       8     1 hello   Ljava/lang/String;
}
SourceFile: "HelloWorld.java"

也可以通过JBE(可直接修改)查看, JClassLib(IDEA插件,上述class文件显示如下)

JClassLib查看class文件
下面分别阐述一下各个字段的含义

the class File Format

maigc

魔数是用来作为文件标识,class文件固定为0xcafebabe,表示可以被JVM识别处理

文件扩展名是可以被随意修改的,因此需要魔数作为文件标识

minor/major version

通过minor_version(m)和major_version(M)决定class文件版本为M.m

其中major version与jdk版本的关系如下表

jdk版本默认major version支持的major version
1.0.24545
1.14545
1.24645 - 46
1.34745 - 46
1.44845 - 46
5.04945 - 46
65045 - 50
75145 - 51
85245 - 52
95345 - 53
105445 - 54
115545 - 55
125645 - 56
135745 - 57

minor version的大小则与major version有关

  • major_version >= 56,mnior_version为0或65535
  • major_version >= 45 and <= 55,minor_version可以为任意值

更细微的区别可以参考官方文档查看

Constant pool (count)

Constant Pool常量池,其大小为(constant_pool_count - 1),此处示例constant_pool_count对应的是0x0010,即常量池大小为15(16 - 1),之所以要减1,是因为index_0被预留,没有指向任何内容。

Constant pool内容较为细致,后续会单独开章来说

Access flags

access_flags表示访问标识符,常见有:

标识名标识值标识含义针对对象
ACC_PUBLIC0x0001public类型所有类型
ACC_FINAL0x0010final类型
ACC_SUPER0x0020使用invokespecial语义类和接口
ACC_INTERFACE0x0200接口类型接口
ACC_ABSTRACT0x0400抽象类型类和接口
ACC_SYNTHETIC0x1000由compiler生成所有类型
ACC_ANNOTATION0x2000注解类型注解
ACC_ENUM0x4000枚举类型枚举
ACC_MODULE0x8000模块类型模块
  • invokespecial是一个字节码指令, 用于调用一个方法, 一般情况下, 调用构造方法或者使用super关键字显示调用父类的方法时, 会使用这条字节码指令。这正是ACC_SUPER这个名字的由来。 在java 1.2之前, invokespecial对方法的调用都是静态绑定的, 而ACC_SUPER这个标志位在java 1.2的时候加入到class文件中, 它为invokespecial这条指令增加了动态绑定的功能。
  • module是自jdk9新增的结构类型,用于包依赖管理,更详细的内容可参考Java 9 Module的特性以及解决的问题
  • ACC_INTERFACE存在,则ACC_ABSTRACT也必须存在,ACC_FINAL | ACC_SUPER | ACC_ENUM | ACC_MODULE则必须不存在
  • ACC_SYNTHETIC表示这个class文件由编译器生成,且没有出现在源码中(即不是用户编写)
  • ACC_ANNOTATION存在,则ACC_INTERFACE必须存在

access_flags的值是通过按位或运算得到,

  • public class,对应的是 0x0001 | 0x0020 = 0x0021,隐性的含有ACC_SUPER
  • public interface,对应的是 0x0001 | 0x0200 | 0x0400 = 0x0601,隐性的含有ACC_ABSTRACT

this class

当前类,其值指向constant pool table

super class

父类,其值指向constant poll table

  • 如果值为0,则代表该类是Object

interfaces (count)

interfaces count表示当前类的接口数量,在类型为class的文件中,为0
interfaces[] 数组大小对应为interfaces count

fields (count)

fields count表示当前类的fields数量,包括类变量和实例变量
fields[] 数组大小为fields count,每个元素结构为field_info

field_info {
	u2 access_flags;
	u2 name_index;
	u2 descriptor_index;
	u2 attributes_count;
	attribute_info attributes[attributes_count];
}

Field Descriptors

FieldDescriptor
FieldType
BaseType
ObjectType
ArrayType
  • BaseType
    • B byte(signed byte)
    • C char(unicode character, encode with UTF-16)
    • D double
    • F float
    • I int
    • J long
    • S short(signed short)
    • Z boolean(true or false)
  • ObjectType
    • (L ClassName 😉
  • ArrayType
    • [ 数组形式,ComponentType, 元素是BaseType ObjectType ArrayType(数组套数组)中的一种

所有的字符均是ASCII字符

methods (count)

methods count表示当前类的方法数量
methods[] 数组大小为fields count,每个元素结构为method_info

method_info {
	u2 access_flags;
	u2 name_index;
	u2 descriptor_index;
	u2 attributes_count;
	attribute_info attributes[attributes_count];
}

method descriptor

每个方法描述包括0或多个参数(parameter descriptors)和1个返回类型(return descriptor),结构如下:

MethodDescriptor:
	({ParameterDescriptor}) ReturnDescriptor
ParameterDescriptor:
	FieldType
ReturnDescriptor:
	FieldType
	VoidDescriptor
VoidDescriptor:
	V

一个示例如下:

Object method(int a, double b, Thread thread) {...}

对应的method descriptor如下

(IDLjava/lang/Thread;)Ljava/lang/Object;
// I --> int a
// D --> double b
// Ljava/lang/Thread; --> Thread thread
//	java/lang/Thread --> Thread的className
// Ljava/lang/Object; --> Object

attributes (count)

attributes count表示当前类的属性数量,包括类变量和实例变量
attributes[] 数组大小为attributes count,每个元素结构为attribute_info

attribute_info {
	u2 attribute_name_index;
	u4 attribute_length;
	u1 info[attribute_length];
}

总结

以上就是class文件格式的主要内容,当然描述的都是相对表象的,下文(class文件2-字节码阅读)通过阅读class文件的形式来说明常量池结构

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值