jvm之.class文件解读(下)(一)

简介: jvm之.class文件解读(下)

访问标志

访问标识(access_flag、访问标志、访问标记)

在常量池后,紧跟着访问标记。该标记使用两个字节表示,用于识别一些类或者接口层次的访问信息,包括:这个 Class 是类还是接口;是否定义为 public 类型;是否定义为 abstract 类型;如果是类的话,是否被声明为 final 等。各种访问标记如下所示:

标志名称 标志值 含义
ACC_PUBLIC 0x0001 标志为 public 类型
ACC_FINAL 0x0010 标志被声明为 final,只有类可以设置
ACC_SUPER 0x0020 标志允许使用 invokespecial 字节码指令的新语义,JDK1.0.2 之后编译出来的类的这个标志默认为真。(使用增强的方法调用父类方法)
ACC_INTERFACE 0x0200 标志这是一个接口
ACC_ABSTRACT 0x0400 是否为 abstract 类型,对于接口或者抽象类来说,次标志值为真,其他类型为假
ACC_SYNTHETIC 0x1000 标志此类并非由用户代码产生(即:由编译器产生的类,没有源码对应)
ACC_ANNOTATION 0x2000 标志这是一个注解
ACC_ENUM 0x4000 标志这是一个枚举

类的访问权限通常为 ACC_开头的常量。

每一种类型的表示都是通过设置访问标记的 32 位中的特定位来实现的。比如,若是 public final 的类,则该标记为 ACC_PUBLIC | ACC_FINAL。

使用 ACC_SUPER 可以让类更准确地定位到父类的方法 super.method(),现代编译器都会设置并且使用这个标记。

 类索引、父类索引、接口索引

在访问标记后,会指定该类的类别、父类类别以及实现的接口,格式如下:

长度 含义
u2 this_class
u2 super_class
u2 interfaces_count
u2 interfaces[interfaces_count]

这三项数据来确定这个类的继承关系:

  • 类索引用于确定这个类的全限定名
  • 父类索引用于确定这个类的父类的全限定名。由于 Java 语言不允许多重继承,所以父类索引只有一个,除了 java.1ang.Object 之外,所有的 Java 类都有父类,因此除了 java.lang.Object 外,所有 Java 类的父类索引都不为 e。
  • 接口索引集合就用来描述这个类实现了哪些接口,这些被实现的接口将按 implements 语句(如果这个类本身是一个接口,则应当是 extends 语句)后的接口顺序从左到右排列在接口索引集合中。

this_class(类索引)

2 字节无符号整数,指向常量池的索引。它提供了类的全限定名,如 com/atguigu/java1/Demo。this_class 的值必须是对常量池表中某项的一个有效索引值。常量池在这个索引处的成员必须为 CONSTANT_Class_info 类型结构体,该结构体表示这个 class 文件所定义的类或接口。

super_class(父类索引)

2 字节无符号整数,指向常量池的索引。它提供了当前类的父类的全限定名。如果我们没有继承任何类,其默认继承的是 java/lang/object 类。同时,由于 Java 不支持多继承,所以其父类只有一个。

super_class 指向的父类不能是 final。

interfaces

指向常量池索引集合,它提供了一个符号引用到所有已实现的接口

由于一个类可以实现多个接口,因此需要以数组形式保存多个接口的索引,表示接口的每个索引也是一个指向常量池的 CONSTANT_Class(当然这里就必须是接口,而不是类)。

Ⅰ. interfaces_count(接口计数器)

interfaces_count 项的值表示当前类或接口的直接超接口数量。

Ⅱ. interfaces[](接口索引集合)

interfaces[]中每个成员的值必须是对常量池表中某项的有效索引值,它的长度为 interfaces_count。每个成员 interfaces[i]必须为 CONSTANT_Class_info 结构,其中 0 <= i < interfaces_count。在 interfaces[]中,各成员所表示的接口顺序和对应的源代码中给定的接口顺序(从左至右)一样,即 interfaces[0]对应的是源代码中最左边的接口。

字段表集合

fields

用于描述接口或类中声明的变量。字段(field)包括类级变量以及实例级变量,但是不包括方法内部、代码块内部声明的局部变量。

字段叫什么名字、字段被定义为什么数据类型,这些都是无法固定的,只能引用常量池中的常量来描述。

它指向常量池索引集合,它描述了每个字段的完整信息。比如字段的标识符、访问修饰符(public、private 或 protected)、是类变量还是实例变量(static 修饰符)、是否是常量(final 修饰符)等。

注意事项:

  • 字段表集合中不会列出从父类或者实现的接口中继承而来的字段,但有可能列出原本 Java 代码之中不存在的字段。譬如在内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段。
  • 在 Java 语言中字段是无法重载的,两个字段的数据类型、修饰符不管是否相同,都必须使用不一样的名称,但是对于字节码来讲,如果两个字段的描述符不一致,那字段重名就是合法的。

字段计数器

fields_count(字段计数器)

fields_count 的值表示当前 class 文件 fields 表的成员个数。使用两个字节来表示。

fields 表中每个成员都是一个 field_info 结构,用于表示该类或接口所声明的所有类字段或者实例字段,不包括方法内部声明的变量,也不包括从父类或父接口继承的那些字段。

标志名称 标志值 含义 数量
u2 access_flags 访问标志 1
u2 name_index 字段名索引 1
u2 descriptor_index 描述符索引 1
u2 attributes_count 属性计数器 1
attribute_info attributes 属性集合 attributes_count

字段表

Ⅰ. 字段表访问标识

我们知道,一个字段可以被各种关键字去修饰,比如:作用域修饰符(public、private、protected)、static 修饰符、final 修饰符、volatile 修饰符等等。因此,其可像类的访问标志那样,使用一些标志来标记字段。字段的访问标志有如下这些:

标志名称 标志值 含义
ACC_PUBLIC 0x0001 字段是否为 public
ACC_PRIVATE 0x0002 字段是否为 private
ACC_PROTECTED 0x0004 字段是否为 protected
ACC_STATIC 0x0008 字段是否为 static
ACC_FINAL 0x0010 字段是否为 final
ACC_VOLATILE 0x0040 字段是否为 volatile
ACC_TRANSTENT 0x0080 字段是否为 transient
ACC_SYNCHETIC 0x1000 字段是否为由编译器自动产生
ACC_ENUM 0x4000 字段是否为 enum

Ⅱ. 描述符索引

描述符的作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。根据描述符规则,基本数据类型(byte,char,double,float,int,long,short,boolean)及代表无返回值的 void 类型都用一个大写字符来表示,而对象则用字符 L 加对象的全限定名来表示,如下所示:

标志符 含义
B 基本数据类型 byte
C 基本数据类型 char
D 基本数据类型 double
F 基本数据类型 float
I 基本数据类型 int
J 基本数据类型 long
S 基本数据类型 short
Z 基本数据类型 boolean
V 代表 void 类型
L 对象类型,比如:Ljava/lang/Object;
[ 数组类型,代表一维数组。比如:`double[][][] is [[[D

Ⅲ. 属性表集合

一个字段还可能拥有一些属性,用于存储更多的额外信息。比如初始化值、一些注释信息等。属性个数存放在 attribute_count 中,属性具体内容存放在 attributes 数组中。

1. // 以常量属性为例,结构为:
2. ConstantValue_attribute{
3.  u2 attribute_name_index;
4.  u4 attribute_length;
5.     u2 constantvalue_index;
6. }

说明:对于常量属性而言,attribute_length 值恒为 2。

方法表集合

methods:指向常量池索引集合,它完整描述了每个方法的签名。

  • 在字节码文件中,每一个 method_info 项都对应着一个类或者接口中的方法信息。比如方法的访问修饰符(public、private 或 protected),方法的返回值类型以及方法的参数信息等。
  • 如果这个方法不是抽象的或者不是 native 的,那么字节码中会体现出来。
  • 一方面,methods 表只描述当前类或接口中声明的方法,不包括从父类或父接口继承的方法。另一方面,methods 表有可能会出现由编译器自动添加的方法,最典型的便是编译器产生的方法信息(比如:类(接口)初始化方法<clinit>()和实例初始化方法<init>())。

使用注意事项:

在 Java 语言中,要重载(Overload)一个方法,除了要与原方法具有相同的简单名称之外,还要求必须拥有一个与原方法不同的特征签名,特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合,也就是因为返回值不会包含在特征签名之中,因此 Java 语言里无法仅仅依靠返回值的不同来对一个已有方法进行重载。但在 Class 文件格式中,特征签名的范围更大一些,只要描述符不是完全一致的两个方法就可以共存。也就是说,如果两个方法有相同的名称和特征签名,但返回值不同,那么也是可以合法共存于同一个 class 文件中。

也就是说,尽管 Java 语法规范并不允许在一个类或者接口中声明多个方法签名相同的方法,但是和 Java 语法规范相反,字节码文件中却恰恰允许存放多个方法签名相同的方法,唯一的条件就是这些方法之间的返回值不能相同。

使用注意事项:

在 Java 语言中,要重载(Overload)一个方法,除了要与原方法具有相同的简单名称之外,还要求必须拥有一个与原方法不同的特征签名,特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合,也就是因为返回值不会包含在特征签名之中,因此 Java 语言里无法仅仅依靠返回值的不同来对一个已有方法进行重载。但在 Class 文件格式中,特征签名的范围更大一些,只要描述符不是完全一致的两个方法就可以共存。也就是说,如果两个方法有相同的名称和特征签名,但返回值不同,那么也是可以合法共存于同一个 class 文件中。

也就是说,尽管 Java 语法规范并不允许在一个类或者接口中声明多个方法签名相同的方法,但是和 Java 语法规范相反,字节码文件中却恰恰允许存放多个方法签名相同的方法,唯一的条件就是这些方法之间的返回值不能相同。

方法计数器

methods_count(方法计数器)

methods_count 的值表示当前 class 文件 methods 表的成员个数。使用两个字节来表示。

methods 表中每个成员都是一个 method_info 结构。

方法表

methods[](方法表)

methods 表中的每个成员都必须是一个 method_info 结构,用于表示当前类或接口中某个方法的完整描述。如果某个 method_info 结构的 access_flags 项既没有设置 ACC_NATIVE 标志也没有设置 ACC_ABSTRACT 标志,那么该结构中也应包含实现这个方法所用的 Java 虚拟机指令。

method_info 结构可以表示类和接口中定义的所有方法,包括实例方法、类方法、实例初始化方法和类或接口初始化方法

方法表的结构实际跟字段表是一样的,方法表结构如下:

标志名称 标志值 含义 数量
u2 access_flags 访问标志 1
u2 name_index 方法名索引 1
u2 descriptor_index 描述符索引 1
u2 attributes_count 属性计数器 1
attribute_info attributes 属性集合 attributes_count

方法表访问标志

跟字段表一样,方法表也有访问标志,而且他们的标志有部分相同,部分则不同,方法表的具体访问标志如下:

标志名称 标志值 含义
ACC_PUBLIC 0x0001 public,方法可以从包外访问
ACC_PRIVATE 0x0002 private,方法只能本类访问
ACC_PROTECTED 0x0004 protected,方法在自身和子类可以访问
ACC_STATIC 0x0008 static,静态方法


相关文章
|
6月前
|
安全 Java
对 JVM 的类加载机制以及寻找字节码文件的“双亲委派模型”的理解
对 JVM 的类加载机制以及寻找字节码文件的“双亲委派模型”的理解
39 0
|
1月前
|
SQL 缓存 Java
JVM知识体系学习三:class文件初始化过程、硬件层数据一致性(硬件层)、缓存行、指令乱序执行问题、如何保证不乱序(volatile等)
这篇文章详细介绍了JVM中类文件的初始化过程、硬件层面的数据一致性问题、缓存行和伪共享、指令乱序执行问题,以及如何通过`volatile`关键字和`synchronized`关键字来保证数据的有序性和可见性。
28 3
|
1月前
|
小程序 Oracle Java
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
这篇文章是关于JVM基础知识的介绍,包括JVM的跨平台和跨语言特性、Class文件格式的详细解析,以及如何使用javap和jclasslib工具来分析Class文件。
41 0
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
|
4月前
|
Java
jmap 查看jvm内存大小并进行dump文件内存分析
jmap 查看jvm内存大小并进行dump文件内存分析
92 3
|
6月前
|
Java 索引
【JVM】字节码文件的组成部分
【JVM】字节码文件的组成部分
54 1
|
5月前
|
存储 Java 编译器
【搞定Jvm面试】 面试官:谈谈 JVM 类文件结构的认识
【搞定Jvm面试】 面试官:谈谈 JVM 类文件结构的认识
|
5月前
|
存储 XML 安全
JVM系列5-类文件结构
JVM系列5-类文件结构
31 0
|
6月前
|
存储 前端开发 Java
深入浅出JVM(四)之类文件结构
深入浅出JVM(四)之类文件结构
深入浅出JVM(四)之类文件结构
|
6月前
|
存储 XML 监控
JVM工作原理与实战(三):字节码文件的组成
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了字节码文件的基础信息、常量池、方法、字段、属性等内容。
93 6
|
6月前
|
存储 Java 索引
《深入浅出Java虚拟机 — JVM原理与实战》带你攻克技术盲区,夯实底层基础 —— 吃透class字节码文件技术基底和实现原理(核心结构剖析)
《深入浅出Java虚拟机 — JVM原理与实战》带你攻克技术盲区,夯实底层基础 —— 吃透class字节码文件技术基底和实现原理(核心结构剖析)
76 0