编程技术是改变世界的力量。
本站
当前位置:网站首页 > 后端语言 > 正文

class字节码文件格式详解 class bytes

gowuye 2024-04-04 11:56 8 浏览 0 评论

class字节码文件格式

掌握jvm 字节码,最关键的是学习class文件格式以及字节码指令集等细节,今天我们来学习class字节码文件格式(jdk8版本)。

Java代码经过javac编译器编译成class文件,JVM虚拟机读取class文件执行其中的代码。

通过JVM虚拟机规范,实现了jvm跨平台、跨语言的能力,JVM规范中非常重要的一部分就是class字节码文件格式。

class文件结构

class文件的整体结构如下图所示,其中u1,u2,u4分别表示1个、2个、4个字节长度的无符号数据,无符号byte数据按照具体的场景可以用来表示数字、字符等。 结构中还可以使用复合结构,比如cp_info, cp_info结构也会在规范中进行定义。

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];
}

magic

魔法字符串,固定为0xCAFEBABE

minor_version, major_version

分别是class文件的小版本号和大版本号,jvm规范要求运行的jvm版本必须大于等于(更严格说是能支持,不过目前大于等于即可)class文件的major_version才能运行,否则抛出异常。

constant_pool_count

常量池数量,是下面的常量池表的长度加一,因为index=0的常量引用没有使用。

constant_pool[]

常量池表,每个常量池的结构cp_info如下,常量池可以表示字符串常量、类名、接口名、方法等信息,这些常量池会在class文件中其他地方进行引用(比如字段中字段类型、字段名等)。 常量通过index进行引用,常量之间也可以通过index进行引用。

cp_info中的tag字段用来标识当前的常量类型,不同的常量类型有不同的子结构,然后就可以用具体的结构来解析info[]这个byte数组。 常量的结构有,

cp_info {
    u1 tag;
    u1 info[];
}

Constant type

tag value

CONSTANT_Class

7

CONSTANT_Fieldref

9

CONSTANT_Methodref

10

CONSTANT_InterfaceMethodref

11

CONSTANT_Integer

3

CONSTANT_Float

4

CONSTANT_Long

5

CONSTANT_Double

6

CONSTANT_NameAndType

12

CONSTANT_Utf8

1

CONSTANT_MethodHandle

15

CONSTANT_MethodType

16

CONSTANT_InvokeDynamic

18

我们提前查看一下各个常量类型的结构,给后面介绍Field, Method做铺垫。

CONSTANT_Class_info {
    u1 tag;
    u2 name_index;
}

CONSTANT_Class_info

CONSTANT_Class_info表示类或接口

CONSTANT_Class_info {
    u1 tag;
    u2 name_index;
}

tag: 是CONSTANT_Class对应的值(7) name_index: name_index是这个类或接口的类名的字符串常量的index

CONSTANT_Fieldref_info, CONSTANT_Methodref_info, CONSTANT_InterfaceMethodref_info

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

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

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

字段引用、方法引用、接口方法引用这三个结构比较类似,都是各自的tag以及class_index和name_and_type_index

class_index: 这个字段、方法所在类的class的常量的index

name_and_type_index: 这个字段的名称和类型结构常量CONSTANT_NameAndType_info的index。name分别是字段名和方法名,类型是字段、方法的descriptor描述符。

CONSTANT_String_info

字符串常量结构

string_index: 指向CONSTANT_Utf8_info的index

CONSTANT_String_info {
    u1 tag;
    u2 string_index;
}

CONSTANT_Integer_info, CONSTANT_Float_info

整数和浮点数常量结构,对应的数值占用4个字节。

CONSTANT_Integer_info {
    u1 tag;
    u4 bytes;
}

CONSTANT_Float_info {
    u1 tag;
    u4 bytes;
}

CONSTANT_Long_info, CONSTANT_Double_info

这两个常量结构分别存储long和double类型的数值,大小占用8个字节 high_bytes和low_bytes分别表示高位和低位的数据,以long为例,对应值为((long) high_bytes << 32) + low_bytes

CONSTANT_Long_info {
    u1 tag;
    u4 high_bytes;
    u4 low_bytes;
}

CONSTANT_Double_info {
    u1 tag;
    u4 high_bytes;
    u4 low_bytes;
}

CONSTANT_NameAndType_info

CONSTANT_NameAndType_info常量用来表示名称和类型,在前面的CONSTANT_Fieldref_info, CONSTANT_Methodref_info, CONSTANT_InterfaceMethodref_info 常量中有使用,结构如下

CONSTANT_NameAndType_info {
    u1 tag;
    u2 name_index;
    u2 descriptor_index;
}

name_index: 指向对应名称的utf8常量的CONSTANT_Utf8_info的index descriptor_index: 指向类型描述符的CONSTANT_Utf8_info的index。

Field Descriptor和Method Descriptor

在jvm中,数据分为primitive type(基本类型,比如int, long)和reference type(引用类型),类型的描述符规则如下

类型

描述符

byte

B

char

C

double

D

float

F

int

I

long

J

short

S

boolean

Z

reference,引用类型

LClassName;

数组

[

引用类型的ClassName是/间隔的字符串,比如java.lang.String的描述符为Ljava/lang/String; 数组是在对应的类型前加[,比如int[]描述符为[I, String[]描述符为[Ljava/lang/String;, 多维数组距离 int[][]描述符为[[I

Field Descriptor是对应字段的类型的描述符 Method Descriptor为( {ParameterDescriptor} ) ReturnDescriptor,比如public String test(int a, Long b)的方法描述符为(ILjava/lang/Long)Ljava/lang/String;,如果返回值是void,则使用V

CONSTANT_Utf8_info

CONSTANT_Utf8_info常量存储utf8编码的字符串内容,包含一个字符串长度字段和对应长度的byte数组。

CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

class access_flags

access_flags用来表示当前类的一些bit信息(类似bitmap),这样用2个字节的空间就可以表示16个标记信息。

Flag Name

Value

表头

ACC_PUBLIC

0x0001

表示当前类/接口是否是public

ACC_FINAL

0x0010

是否声明了final

ACC_SUPER

0x0020

都是true, 为了兼容旧版本的字节码的标记

ACC_INTERFACE

0x0200

是否是接口

ACC_ABSTRACT

0x0400

是否是抽象类,接口也是抽象类

ACC_SYNTHETIC

0x1000

表示不是代码中生成的类,比如jdk为实现lambda表达式在运行时生成的一些类

ACC_ANNOTATION

0x2000

是否是@interface这样的注解类

ACC_ENUM

0x4000

是否枚举类

Fields

Fields是field_info的数组,每个field_info结构如下。

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

access_flags: 字段的access_flags,和class的access_flags类似,用来描述字段的public,private,volatile等等标识信息。

Flag Name

Value

描述

ACC_PUBLIC

0x0001

是否是public字段

ACC_PRIVATE

0x0002

是否是private字段

ACC_PROTECTED

0x0004

是否是static字段

ACC_STATIC

0x0008

是否是static字段

ACC_FINAL

0x0010

是否是final字段

ACC_VOLATILE

0x0040

是否是volatile字段

ACC_TRANSIENT

0x0080

是否是transient字段

ACC_SYNTHETIC

0x1000

单元格

ACC_ENUM

0x4000

单元格

name_index: 字段名称的CONSTANT_Utf8_info常量index descriptor_index: 字段类型描述符的CONSTANT_Utf8_info常量index attributes_count: 字段的属性数量 attributes: 字段的属性,结构为attribute_info,比如ConstantValue,描述常量字段的常量值,属性的结构稍后介绍。

Methods

类中所有的方法包括构造函数(<init>)、静态初始化方法(<clinit>),都使用method_info结构,在一个类中,方法名称和方法签名联合起来必须唯一

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

access_flags: 方法的标识数据,包括public, private, synchronized等等信息

Flag Name

Value

描述

ACC_PUBLIC

0x0001

public方法

ACC_PRIVATE

0x0002

private方法

ACC_PROTECTED

0x0004

protected方法

ACC_STATIC

0x0008

static方法

ACC_FINAL

0x0010

final方法

ACC_SYNCHRONIZED

0x0020

synchronized方法(方法维度的synchronized声明,不同于synchronized代码块的monitor_enter和monitor_exit)

ACC_BRIDGE

0x0040

是否是transient字段

ACC_VARARGS

0x0080

有可变参数的方法

ACC_NATIVE

0x0100

native方法

ACC_ABSTRACT

0x0400

抽象方法

ACC_STRICT

0x0800

浮点数模式是FT-strict的,这个很少见

ACC_SYNTHETIC

0x1000

是否是合成方法,即不再源代码中的方法

name_index: 指向方法名的CONSTANT_Utf8_info常量 descriptor_index: 指向方法描述符的CONSTANT_Utf8_info常量 attributes_count: 方法的属性数量 attributes[]: 方法的各个属性,其中比较关键的是名字为Code的属性,包含的是方法体的字节码指令。

Attributes属性

Attributes属性在classfile, field_info, method_info中都有使用,结构如下

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

attribute_name_index: 指向属性的名称的CONSTANT_Utf8_info常量 attribute_length: 属性信息的字节长度,即info的长度 info[]: 属性的具体信息,每种属性有自己的结构

属性有ConstantValue,Code,StackMapTable,Exceptions,BootstrapMethods等等很多种属性,我们这里重点介绍一下ConstantValue和Code。

ConstantValue属性

常量值属性用来表示常量字段的常量值,数值(int,long,float等)和字符串字段能够声明成常量。

ConstantValue_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 constantvalue_index;
}

attribute_name_index: 指向"ConstantValue"的CONSTANT_Utf8_info attribute_length: 2,因为constantvalue_index是两个byte长度的index constantvalue_index: 指向具体的常量池中的常量,按照类型不同分为CONSTANT_Long,CONSTANT_Float,CONSTANT_Double,CONSTANT_Integer(int, short, char, byte, boolean都用CONSTANT_Integer),CONSTANT_String,

Code属性

Code属性用来表示方法体中的代码字节码。

Code_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 max_stack;
    u2 max_locals;
    u4 code_length;
    u1 code[code_length];
    u2 exception_table_length;
    {   u2 start_pc;
        u2 end_pc;
        u2 handler_pc;
        u2 catch_type;
    } exception_table[exception_table_length];
    u2 attributes_count;
    attribute_info attributes[attributes_count];
}

attribute_name_index: 指向"Code"的CONSTANT_Utf8_info常量 attribute_length: 后面所有的字段信息的字节数 max_stack: 方法的字节码指令执行过程中需要的操作数栈的最大栈层数,关于方法字节码指令的执行,在字节码指令文章中进行介绍。 max_locals: 方法的字节码指令执行过程中需要的本地变量表的最大长度(注意局部变量表的元素长度是4字节,long和double变量在局部变量表中占两个位置) code_length: 方法体的字节码的长度 code[]: 方法体的字节码 exception_table_length: 异常表的长度 exception_table[]: 异常表数组,每个异常表包含start_pc,end_pc,handler_pc,catch_type。pc是指code[]数组中的索引,也就是从code[]字节码数组start_pc(包含)到end_pc(不包含)中的字节码执行时出现catch_type(指向异常类的CONSTANT_Class_info常量)异常,则转到code[]的handler_pc位置来处理异常。 attributes_count: Code属性的数量 attributes[]: Code属性数组,比如LineNumberTable,LocalVariableTable, LocalVariableTypeTable, StackMapTable

其他的属性可以参考jvm规范

如何解析class文件

假如我们现在有一个class文件,想去查看其中的Java源代码,该如何实现呢?有如下几种方法。

通过javap

javap是jdk里自带的反编译工具,可以打印出更加可读的class字节码信息。

javap -c -cp /Users/liuzhengyang/Code/work/code-test/target/classes/ test.Test

Compiled from "Test.java"
public class test.Test {
  public test.Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public java.lang.String hello();
    Code:
       0: ldc           #2                  // String hello world
       2: areturn
}

javap参数说明

参数

说明

-cp

指定classpath, javap需要到classpath中寻找class文件

-p

默认情况下javap不打印出private的方法、字段,通过-p可以打印全部信息

-c

默认情况下javap不打印出方法的body字节码,通过-c可以打印

-v

打印最全的信息,包括常量池、方法stack size、方法本地变量表等等

通过IDEA反编译

把class文件拖动到IDEA中即可查看到反编译的java代码结果,相比javap更加易读。

通过arthas jad命令

如果要查看运行中的程序中使用到的代码,可以使用arthas的[jad](https://arthas.aliyun.com/doc/jad.html)命令。

更多资料

更详细的资料包括java语言规范、java虚拟机规范可以在[Java Language and Virtual Machine Specifications](https://docs.oracle.com/javase/specs/index.html)中找到

总结

本篇文章介绍了class文件的结构,包括常量池、字段、方法、属性等,详细了解了每个数据的结构,最后了解查看class文件的几种方式。

相关推荐

爱上开源之golang入门至实战第四章-切片(Slice)

前言Go数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可...

Go语言入门必知教程-切片

切片是一种灵活的和可扩展的数据结构,用于实现和管理数据集。切片由多个元素组成,所有元素都是相同类型的。切片是动态数组的一部分,可以根据需要进行增长和收缩。与数组一样,切片也可以索引。切片具有容量和长度...

Go语言基础-切片

切片是什么?切片是Go语言的一种数据结构。和数组相似,不过切片可以在它的结尾增加更多的元素。这样可变长度在实际编程中更为有用。声明切片切片的声明和数组也很相似,只是声明切片时不需要指定大小。例:va...

5分钟掌握GO中切片的基本使用方法

最近Golang越来越火,不少小伙伴都纷纷开始学习Golang,但对于原先为C++或者JAVA的同学,用习惯了数据、list、vector等,会对Go的切片slice不习惯,下面整理出go中slice...

揭秘 Go 切片(Slice)的秘密

当向切片添加新参数时,底层数组会发生什么变化?它会扩展以容纳更多元素吗?在这篇文章中,我们将深入探讨切片的内部工作原理,以及如何利用这些知识来进行更好的内存管理和性能优化。具体而言,我们将探索Go...

【Go语言slice详解】深入掌握Go语言中的slice类型及常用操作!

Go语言中的slice(切片)是一种非常方便的数据结构,可以动态地增加或减少其元素数量,且可以访问底层数组的任意一个子序列。本文将对Go语言中的slice进行详细的讲解。Slice的定义在Go语言中,...

掌握GO中的Slice,这就够了

最近Golang越来越火,不少小伙伴都纷纷开始学习Golang,但对于原先为C++或者JAVA的同学,用习惯了数据、list、vector等,会对Go的切片slice不习惯,下面整理出go中slice...

golang2021面向对象(26)Go语言类型内嵌和结构体内嵌

结构体可以包含一个或多个匿名(或内嵌)字段,即这些字段没有显式的名字,只有字段的类型是必须的,此时类型也就是字段的名字。匿名字段本身可以是一个结构体类型,即结构体可以包含内嵌结构体。?可以粗略地将这个...

2022-11-13:以下go语言代码中,如何获取结构体列表以及结构体内

2022-11-13:以下go语言代码中,如何获取结构体列表以及结构体内的指针方法列表?以下代码应该返回{"S1":["M1","M2"],"S...

Go语言文件和目录操作

文件和目录操作概述一、文件和目录操作概述在计算机中,文件和目录是存储数据的重要方式。在Go语言中,我们可以使用os和io/ioutil包提供的函数和结构体来进行文件和目录操作。本文将详细介绍Go语言中...

跟我一起学习go语言(五)golang中结构体的初始化方法

1、自定义一个结构体typeVertexstruct{X,Yfloat64}2、初始化方法-指针:rect1:=new(Vertex)rect2:=&Vertex...

Go复合数据类型:结构体

一种通用的、对实体对象进行聚合抽象的能力,在Go中,提供这种聚合抽象能力的类型是结构体类型,也就是struct。自定义一个新类型在Go中,我们自定义一个新类型一般有两种方法。第一种是类型定义...

Go语言基础:方法

导读在阅读本文章前,假定你具备如下能力:?已掌握结构体1.方法1.1方法的概念在理解程序中方法的概念时,我们先看看现实中的一些情况,这样相对比较好理解一些。在农村的朋友可能会知道,在医疗落后的情况...

为什么 Go 语言 struct 要使用 tags

在Go语言中,struct是一种常见的数据类型,它可以用来表示复杂的数据结构。在struct中,我们可以定义多个字段,每个字段可以有不同的类型和名称。除了这些基本信息之外,Go还提供了s...

一文带你掌握掌握 Golang结构体与方法

1.Golang结构体的概念及定义结构体是Golang中一种复合类型,它是由一组具有相同或不同类型的数据字段组成的数据结构。结构体是一种用户自定义类型,它可以被用来封装多个字段,从而实现数据的...

取消回复欢迎 发表评论: