• Java高级——Class文件及解析


    Class文件

    Java 虚拟机具有平台无关性语言无关性,只与Class文件关联

    Class文件对应类或接口的定义信息,但反之不成立(类或接口可动态生成并送入类加载器

    Class文件以8kb为单位,当大于8kb时,高位在前,包含

    • 无符号数:u1、u2、u4、u8代表1-2-4-8kb,描述数字,引用等
    • 表:由无符号数或表构成,以_info结尾

    当需要描述多个同一类型数据时,可用前置容量计数器+若干个连续的数据项,称为集合

    在这里插入图片描述

    各字段解析

    以下面程序为例,用JDK6编出class文件

    public class Test {
    
    	private int m;
    	
    	public int inc() {
    		return m + 1;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    WinHex打开如下

    在这里插入图片描述

    下文将对上图逐项翻译成如下Javap 的反编译结果

    Compiled from "Test.java"
    public class Test extends java.lang.Object
      SourceFile: "Test.java"
      minor version: 0
      major version: 50
      Constant pool:
    const #1 = class	#2;	//  Test
    const #2 = Asciz	Test;
    const #3 = class	#4;	//  java/lang/Object
    const #4 = Asciz	java/lang/Object;
    const #5 = Asciz	m;
    const #6 = Asciz	I;
    const #7 = Asciz	;
    const #8 = Asciz	()V;
    const #9 = Asciz	Code;
    const #10 = Method	#3.#11;	//  java/lang/Object."":()V
    const #11 = NameAndType	#7:#8;//  "":()V
    const #12 = Asciz	LineNumberTable;
    const #13 = Asciz	LocalVariableTable;
    const #14 = Asciz	this;
    const #15 = Asciz	LTest;;
    const #16 = Asciz	inc;
    const #17 = Asciz	()I;
    const #18 = Field	#1.#19;	//  Test.m:I
    const #19 = NameAndType	#5:#6;//  m:I
    const #20 = Asciz	SourceFile;
    const #21 = Asciz	Test.java;
    
    {
    public Test();
      Code:
       Stack=1, Locals=1, Args_size=1
       0:	aload_0
       1:	invokespecial	#10; //Method java/lang/Object."":()V
       4:	return
      LineNumberTable: 
       line 1: 0
    
      LocalVariableTable: 
       Start  Length  Slot  Name   Signature
       0      5      0    this       LTest;
    
    
    public int inc();
      Code:
       Stack=2, Locals=1, Args_size=1
       0:	aload_0
       1:	getfield	#18; //Field m:I
       4:	iconst_1
       5:	iadd
       6:	ireturn
      LineNumberTable: 
       line 6: 0
    
      LocalVariableTable: 
       Start  Length  Slot  Name   Signature
       0      7      0    this       LTest;
    
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    magic、minor_version、major_version

    Magic Number用于确定是否为Class文件,值为0xCAFEBABE

    在这里插入图片描述
    minor_version为次版本号,major_version为主版本号,如上JDK6编译的class版本号为50

    在这里插入图片描述

    JDK版本版本号
    JDK1458
    JDK1559
    JDK1660
    JDK1761
    JDK1862

    次版本号在JDK1.2后已弃用(固定为0),但在JDK12后表示当前Class文件中是否使用了预览功能,若使用则为65535

    constant_pool_count

    常量池计数器,从1开始,第0项常量表示不引用任何一个常量池项目,如下表示有1-21共22个常量

    在这里插入图片描述

    constant_pool

    常量池主要存放

    • 字面量:字符串、final常量等
    • 符号引用:包、类或接口的全限定名、描述符等

    常量含义

    常量池每一项都是一个表,第一位代表tag代表类型
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    常量池解析

    在这里插入图片描述

    如上常量1的tag为0x07,即CONSTANT_Class_info,结构如下

    在这里插入图片描述
    name_index表示类或接口的符号引用,值为0x0002,表示指向常量2

    在这里插入图片描述

    如上常量2的tag为0x01,即CONSTANT_Utf8_info,结构如下

    在这里插入图片描述
    length表示字符长度,bytes表示UTF-8缩略码

    • ‘\u0001’ 到 ‘\u007f’(即1-127的ASCII码)用一个字节表示
    • ‘\u0080’ 到 ‘\u07ff’ 用两个字节表示
    • ‘\u0800’ 到 ‘\uffff’ 用三个字节表示

    上图中length为0x0004,即后续4字节为常量内容,翻译成ASCII为类名Test

    • 方法、字段名都是用CONSTANT_Utf8_info描述,其最大长度为u2,即65535,超过会无法编译

    常量池项

    如下为截取的全部常量池项

    const #1 = class	#2;	//  Test
    const #2 = Asciz	Test;
    const #3 = class	#4;	//  java/lang/Object
    const #4 = Asciz	java/lang/Object;
    const #5 = Asciz	m;
    const #6 = Asciz	I;
    const #7 = Asciz	;
    const #8 = Asciz	()V;
    const #9 = Asciz	Code;
    const #10 = Method	#3.#11;	//  java/lang/Object."":()V
    const #11 = NameAndType	#7:#8;//  "":()V
    const #12 = Asciz	LineNumberTable;
    const #13 = Asciz	LocalVariableTable;
    const #14 = Asciz	this;
    const #15 = Asciz	LTest;;
    const #16 = Asciz	inc;
    const #17 = Asciz	()I;
    const #18 = Field	#1.#19;	//  Test.m:I
    const #19 = NameAndType	#5:#6;//  m:I
    const #20 = Asciz	SourceFile;
    const #21 = Asciz	Test.java;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    对应图中如下选中部分
    在这里插入图片描述

    access_flag

    描述类或接口的访问信息,选项如表

    在这里插入图片描述

    如Test类的标志值为ACC_PUBLIC和ACC_SUPER,即0x0001 | 0x0020 = 0x0021

    在这里插入图片描述

    this_class

    描述类的全限定名,引用常量池项,如下指向常量1,而1指向2,即类名Test

    在这里插入图片描述

    super_class

    描述类所继承父类的全限定名,引用常量池项,除Object外,所有java类的super_class都不为0,如下指向常量3,而3指向4,即java/lang/Object

    在这里插入图片描述

    interfaces_count、interfaces

    描述类所实现接口的全限定名,引用常量池项,若没实现任何接口则为0,后面不再占用字节

    在这里插入图片描述

    fields_count、fields

    描述接口或类中声明的变量,包括类变量和实例变量,不包括方法内的局部变量,结构如下

    在这里插入图片描述

    access_flags为修饰符,选项如下

    • ACC_PUBLIC、ACC_PRIVATE、ACC_PROTECTED三选一
    • ACC_FINAL、ACC_VOLATILE冲突
    • 接口中包含ACC_PUBLIC、ACC_STATIC、ACC_FINAL

    在这里插入图片描述

    name_index为名称,引用常量池项,即变量名

    descriptor_index为描述符,引用常量池项,用于描述字段的数据类型、方法的参数列表和返回值,选项如下

    在这里插入图片描述

    • 数组用 [ 表示,如String[][] 记录为[[Ljava/lang/String

    • 描述方法时先参数列表后返回值并放在括号内,如int indexOf(char[] arr)描述为([C)I

    attributes_count和attributes描述额外信息,引用常量池项,若字段赋值,则会多一项Contstant Value的属性

    在这里插入图片描述

    • fields_count = 0x0001,只有一个变量
    • access_flags = 0x0002,即private
    • name_index = 0x0005,指向常量5=m
    • descriptor_index = 0x0006,指向常量6=I,int
    • attributes_count = 0x0000,无attributes

    此外在fileds中

    • 不会列出从父类或父接口继承的字段
    • 可能出现原代码中不存在的字段,如内部类会添加指向外部类实例的字段
    • java中字段需使用不同名字,但class中只要字段描述符不同,字段即可重名

    methods_count、methods

    描述类或接口中的方法定义(方法体代码编译成字节码指令存放在attributes中的Code属性),结构如下

    在这里插入图片描述

    access_flags为修饰符,方法无volatile、transient,新增synchronized、native、strictfp、abstract,选项如下

    在这里插入图片描述
    在这里插入图片描述

    methods_count = 0x0002,存在构造方法和源码中的inc()

    下面只介绍

    • access_flags = 0x0001,构造方法为public
    • name_index = 0x0007,指向常量7=
    • descriptor_index = 0x0008,指向常量8=()V,无参数返回void
    • attributes_count = 0x0001,存在一个attributes_info
    • 后面内容为Code attribute,在下面讲解

    此外,对于methods

    • 不会列出子类未复写的父类方法
    • 可能出现原代码中不存在的方法,如()和()
    • Java中方法签名包括方法名称、参数顺序及参数类型,而class签名还包括方法返回值以及受查异常表

    attributes_count、attributes

    Class、Field、Method都可以携带attributes,无严格顺序,但属性名不可重复,结构如下
    在这里插入图片描述
    attribute_length 表示属性值长度,为属性表长度减去6u(即减去attribute_name_index和attribute_length长度)

    attribute_name_index代表属性名,引用常量池,选项如下
    在这里插入图片描述
    在这里插入图片描述

    code

    描述方法体代码,接口或抽象类方法无此属性,结构如下

    在这里插入图片描述
    max_stack代表了操作数栈最大深度,方法执行时不会超过此值,运行时需根据此值分配栈帧中的操作栈深度

    max_locals代表了局部变量表所需的存储空间,单位为Slot

    • 并非所有局部变量所占Slot之和
    • Slot可复用,当超出某局部变量的作用域时,其所占Slot可被其他局部变量使用
    • 编译器会根据变量的作用域来分配Slot
    • 根据同时生存的最大局部变量数量和类型计算出max_locals的大小

    code_length代表字节码指令长度,实际只用了u2,超过65535条字节码指令会编译失败

    code是字节码指令,其取值可看文章字节码指令

    exception_table_length表示异常处理表长度

    exception_table 为异常处理表,结构如下

    在这里插入图片描述

    • 若[start_pc,end_pc)行出现了catch_type或其子类异常,则转到第handler_pc行处理
    • 若catch_type=0,则任意异常都转到handler_pc行处理

    在这里插入图片描述

    如上图,刚刚说到方法中attributes_count = 0x0001,存在一个attributes_info

    • attribute_name_index = 0x0009,指向常量9=Code
    • attribute_length = 0x0000002F,即attribute长度为2F
    • max_stack = 0x0001
    • code_length = 0x00000005,即接下来5字节是字节码指令
    • 2A对于指令为aload_0,将第0个变量槽中reference变量推送到操作数栈顶
    • B7对应指令invokespecial,调用栈顶的reference所指向对象的构造方法、private方法或父类方法,后跟一个u2的参数
    • 000A指向常量10=(),为invokespecial的参数
    • B1对应指令return
    • exception_table_length = 0x0000,即无异常表
    • attributes_count = 0x0002,存在两个attributes_info
    • 后面内容为LineNumberTable和LocalVariableTable attributes,在下面讲解

    上图对应javap

    Code:
       Stack=1, Locals=1, Args_size=1
       0:	aload_0
       1:	invokespecial	#10; //Method java/lang/Object."":()V
       4:	return
    
    • 1
    • 2
    • 3
    • 4
    • 5

    Args_size = 1表示的是this

    • 即this其实是作为参数传入实例方法
    • 实例方法的局部变量表中至少存在this,存放在第一个Slot
    • static方法无this,Args_size = 0
    LineNumberTable

    描述源码行号与字节码行号之间的对应关系

    • 并非运行时必须,但默认生成
    • 可使用-g:none或-g:lines取消生成
    • 若不生成,当抛出异常时不会显示出错的行号,且调试无法设置断点

    在这里插入图片描述
    line_number_talbe_length表示对应关系的数量

    line_number_info包含两个u2的start_pc和line_number,表示字节码行号和源码行号

    在这里插入图片描述

    • attribute_name_index = 0x000C,指向常量C=LineNumberTable
    • attribute_length = 0x00000006,属性长度为6
    • line_number_table_length = 0x0001,即一个对应关系
    • start_pc = 0x0000,line_number = 0x0001,即0映射到1

    对应的javap中内容为

    LineNumberTable: 
       line 1: 0
    
    • 1
    • 2
    LocalVariableTable、LocalVariableTypeTable

    用于描述栈帧中局部变量表的变量与Java源码中定义的变量之间的关系,如下

    • 非运行时必须,但默认生成
    • 可使用-g:none或-g:vars取消生成
    • 若没有生成,引用该方法时参数名会丢失变为arg0、arg1

    在这里插入图片描述
    local_variable_info代表了一个栈帧与源码中的局部变量的关联,如下
    在这里插入图片描述

    • start_pc 表示局部变量的生命周期开始的字节码偏移量
    • length 表示作用范围长度
    • name_index 表示名称
    • descriptor_index 表示描述符
    • index 表示栈帧的局部变量表中Slot的位置

    JDK5后新增LocalVariableTypeTable,把descriptor_index替换成了字段的签名,用于泛型

    在这里插入图片描述

    • attribute_name_index = 0x000D,指向常量D = LocalVariableTable
    • attribute_length = 0x0000000C,长度为12
    • local_variable_length = 0x0001,有一个局部变量
    • start_pc = 0x0000,作用域从0开始
    • length = 0x0005,作用域长度为5
    • name_index = 0x000E,指向常量E = this
    • descriptor_index = 0x000F,指向常量F = LTest,即Test对象
    • index = 0x0000,solt = 0

    对应javap的内容为

    LocalVariableTable: 
       Start  Length  Slot  Name   Signature
       0      5      0    this       LTest;
    
    • 1
    • 2
    • 3

    至此,init()方法就讲解完了,后面是inc()方法不再赘述,如下

    在这里插入图片描述

    Exception

    描述方法可能抛出的Checked Exception,即throws后面的声明的异常,结构如下

    在这里插入图片描述
    number_of_exceptions表示可能抛出多少种Checked Exception

    exception_index_table表示Checked Exception的类型

    SourceFile、SourceDebugExtension

    描述生成这个Class文件的源码文件名称

    • 可选,可通过-g:none或-g:source关闭生成
    • 若不生成,当抛出异常时不显示出错代码所属的文件名

    在这里插入图片描述
    sourcefile_index为文件名

    JDK5后新增SourceDebugExtension用于存储额外的代码调试信息
    在这里插入图片描述
    debug_extension存储额外的调试信息,一个类中最多只允许存在一个SourceDebugExtension属性

    ConstantValue

    通知虚拟机自动为静态变量赋值,只有static变量才能使用

    • 非static变量的赋值是在()方法
    • 若为final static变量且数据类型为基本类型或String,用ConstantValue属性初始化
    • 若非final的static变量,或并非基本类型及字符串,用()方法初始化,因为ConstantValue只能指向基本类型或String的常量池引用

    在这里插入图片描述
    attribute_length值固定为2,即constantvalue_index的长度

    constantvalue_index表示常量池项引用,即变量值

    InnerClasses

    描述内部类与宿主类之间的关联

    在这里插入图片描述

    number_of_classes表示内部类个数,inner_classes_info结构如下

    在这里插入图片描述
    inner_class_info_index/outer_class_info_index为内/外部类符号引用

    inner_name_index 为内部类名称,若匿名内部类为0

    inner_class_access_flags为内部类访问标志,选项如下

    在这里插入图片描述

    Deprecated、Synthetic

    Deprecated表示某个类、字段或者方法,已不再推荐使用,通过@deprecated设置

    Synthetic表示此字段或方法并不是由源码直接产生的,而是由编译器自行添加的,但不包括init和clinit

    在这里插入图片描述

    attribute_length = 0,因为无需设置值,有attribute_name_index即代表存在该属性

    StackMapTable

    一个Code只能有一个StackMapTable,用于类型检查验证器以代替类型推导验证器,省略了运行时的类型推导,转为在编译阶段将验证类型记录在Class

    StackMapTable包含栈映射帧存储字节码偏移量,用于表示执行到该字节码时局部变量表和操作数栈的验证类型

    类型检查验证器会通过检查目标方法的局部变量和操作数栈所需要的类型来确定一段字节码指令是否符合逻辑约束

    在这里插入图片描述

    Signature

    描述泛型签名信息,用于反射API获取泛型类型(如果没有这个属性,泛型擦除后获取不到

    在这里插入图片描述
    signature_index表示类签名或方法类型签名或字段类型签名的引用

    BootstrapMethods

    描述invokedynamic指令引用的引导方法限定符

    在这里插入图片描述
    num_bootstrap_methods表示引导方法个数,bootstrap_method结构如下
    在这里插入图片描述
    bootstrap_method_ref表示引导方法

    num_bootstrap_arguments表示方法参数个数

    bootstrap_arguments表示参数

    MethodParameters

    描述方法各个形参的名称和信息,若没有这个属性,调用class文件的方法时不会有参数提示

    在这里插入图片描述

    parameter_count表示参数个数,parameter结构如下

    在这里插入图片描述
    name_index为参数名字,access_flags为参数类型

    • 0x0010(ACC_FINAL):final
    • 0x1000(ACC_SYNTHETIC)编译器自动生成
    • 0x8000(ACC_MANDATED):源文件中隐式定义,如this
    Module

    描述模块名称、版本、标志等

    在这里插入图片描述
    module_name_index为模块名,module_flag选项如下

    • 0x0020(ACC_OPEN):开放模块
    • 0x1000(ACC_SYNTHETIC):模块是编译器自动生成的
    • 0x8000(ACC_MANDATED):模块是在源文件中隐式定义的

    module_version_index为模块版本号

    requires、exports、opens、uses和provides差不多,只介绍exports

    在这里插入图片描述
    exports_index被该模块导出的包,exports_flags是该导出包的状态,选项如下

    • 0x1000(ACC_SYNTHETIC):导出包是编译器自动生成
    • 0x8000(ACC_MANDATED):导出包在源文件中隐式定义

    exports_to_count为导出包的限定计数器

    • 为零则完全开放的,其他模块可访问该包
    • 不为零,则exports_to_index指定模块才被允许访问该导出包
    ModulePackages

    描述模块中所有的包,不论是不是被export或者open的

    在这里插入图片描述
    package_count是计数器,package_index表示当前模块中的包

    ModuleMainClass

    在这里插入图片描述
    main_class_index代表了该模块的主类

    RuntimeVisibleAnnotations

    描述类、字段或方法的声明上记录运行时可见注解,用于反射获取注解
    在这里插入图片描述
    num_annotations是计数器,annotations是运行时可见的注解,结构如下
    在这里插入图片描述
    type_index是注解类型,element_value_pairs元素为注解参数和值的键值对

    类似的还有

    • RuntimeInvisibleAnnotations
    • RuntimeVisibleParameterAnnotations
    • RuntimeInvisibleParameterAnnotations
    • RuntimeVisibleTypeAnnotations
    • RuntimeInvisibleTypeAnnotations
  • 相关阅读:
    JSP pageContext对象
    Java 核心技术卷 I —— 第1章 Java 程序设计概述
    VScode默认输出到调试控制台如何调整到终端以及两者中的乱码问题
    ChatGPT作者John Schulman:我们成功的秘密武器
    JavaScript随手笔记---数组中相同的元素进行分组(数据聚合) groupBy函数
    Java项目防止SQL注入的四种方案
    (附源码)springboot优课在线教学系统 毕业设计 081251
    S(Stockwell)变换的Matlab代码
    前后端分离项目验证码实现
    Linux·驱动中的并发
  • 原文地址:https://blog.csdn.net/qq_35258036/article/details/127372755