写点什么

《深入浅出 Java 虚拟机 — JVM 原理与实战》带你攻克技术盲区,夯实底层基础 —— 吃透 class 字节码文件技术基底和实现原理(核心结构剖析)

作者:洛神灬殇
  • 2023-07-13
    江苏
  • 本文字数:5211 字

    阅读完需:约 17 分钟

《深入浅出Java虚拟机 — JVM原理与实战》带你攻克技术盲区,夯实底层基础 —— 吃透class字节码文件技术基底和实现原理(核心结构剖析)

特殊字符串

常量池中包含了符号引用,其中包括三种特殊的字符吊:全限定名简单名称描述符



所有的符号引用都包括类或接口的全限定名


  • 字段的符号引用除了全限定类型名之外,还包括简单字段名字段描述符

  • 方法的符号引用除了全限定类型名之外,还包括简单方法名方法描述符


特殊字符串在符号引用中的使用也用于描述被 class 文件定义的类接口


例如,已定义的类或接口将有一个全限定名。对于每个在类或接口中声明的字段,常量池都会包含一个简单名称字段描述符。对于每个在类或接口中声明的方法,常量池也会包含一个简单名称方法描述符

全限定名

当常量池中的符号引用指向类或接口时,它们提供了该类或接口的全限定名。在 class 文件中,全限定名中的点号被斜线替代。


例如,在 class 文件中,java.lang.Object 的全限定名表示为 java/lang/Object;在 class 文件中,java.util.Hashtable 的全限定名表示为 java/util/Hashtable

简单名称

字段名和方法名以简单名称(非全限定名)的形式出现在常量池中。


例如,一个指向类 java.lang.Object 所属方法 String toString()的常量池入口将具有一个形如“toString”的方法名。一个指向类 java.lang.System 所属字段 java.io.PrintStream out 的常量池入口将具有一个形如“out”的字段名。

描述符

除了类(或接口)的全限定名和简单字段(或方法)名,指向字段和方法的符号引用还包含描述符字符串



字段的描述符给出字段的类型,而方法描述符给出方法的返回值以及方法参数的数量、类型和顺序


字段和方法的描述符由以下上下文无关语法定义。在该语法中,非终结符用斜体字表示,例如 FieldType;终结符使用等宽字体表示,例如 B 或 V。


FieldDescriptor:  FieldTypeComponentType:  FieldTypeFieldType:  Base Type  ObjectType  Array TypeBaseType:  B  C  D  E  I  J  S  zObjectType:  L<classname>;ArrayType:  [ ComponentTypeMethodDescriptor:  (ParameterDescriptor) *ReturnDescriptorParameterDescriptor:  FieldTypeReturnDescriptor:  FieldType  V
复制代码


星号表示紧接在其前面的符号(中间没有空格)可以出现 0 次或多次。

基本类型终结符

V 终结符表示方法返回值为 void 类型。八种基本类型终结符中的每一个,返回值描述符终结符 V,对象类型终结符 L 和数组类型终结符[,以及方法描述符终结符(和),都是 ASCII 字符(除了空字符 null 外)。可以使用相应的 ASCII 字符值来描述这些字符。


对象类型中的 ClassName 部分为全限定名,全限定名与 class 文件中的全限定名一样,都用斜线取代了点。



注意:实例方法的方法描述符并不包含作为第一个参数被传递给所有实例方法的隐藏的 this 参数


所有调用实例方法的 Java 虚拟机指令都会隐式传递 this 参数(代表当前对象的引用)。需要注意的是,this 引用仅仅会被传递给实例方法,而不会被传递给类方法,因为类方法不是由对象调用的。


方法描述符只能包含 255 个字长以内的参数。对于实例方法,隐藏的 this 参数占用一个字长,而基本类型 long 或 double 占用两个字长,其他参数占用一个字长。因此,在设计方法时需要考虑参数的数量和类型,以确保方法描述符在规定的字长范围内。

字段描述符示例

方法描述符示例

整体节结构模型为:(参数类型, ......) 返回类型


常量池

常量池是一个有序序列,可变长度的 cp-info 表。cp-info 表的通常形式如下图【cp_info 表的通常形式】所示。



在 cp-info 表中,tag(标志)项是一个无符号的字节类型值,用于表示表的类型和格式。总共有 11 种类型的 cp-info 表,将在后面介绍说明。

CONSTANT_Utf8_info 表

可变长度的 CONSTANT_Utf8_info 表使用一种 UTF-8 格式的变体来存储一个常量字符串。这种类型的表可以存储多种字符串,包括一下内容:


  • 文字字符串,如 String 对象

  • 被定义的类和接口的全限定名

  • 被定义的类的超类(如果有的话)的全限定名

  • 被定义的类和接口的父接口的全限定名。

  • 由类或者接口中的任意字段的简单名称和描述符。

  • 由类或者接口中的任意方法的简单名称和描述符。

  • 任何引用的类和接口的全限定名

  • 任何引用的字段的简单名称和描述符

  • 任何引用的方法的简单名称和描述符

  • 与属性相关的字符串。

CONSTANT_Utf8_info 表的基本类型信息

CONSTANT_Utf8_info 表中存储了四种基本信息类型:字面字符串、被定义的类和接口的描述、对其他类或接口的符号引用以及与属性相关的字符串


属性相关的字符串

一些与属性相关的字符串如:属性名称、生成该 class 文件的源文件名称、局部变量的名称以及描述符。


UTF-8 编码模式允许字符中的所有 Unicode 字符以 2 个字节的形式表示,而 ASCII 字符(空字符 null 除外)以一个字节的形式表示。下面列出了 CONSTANT_Utf8_info 的格式。



CONSTANT_Utf8info 表屮各项如下:

Tag

Tag 项的值为 CONSTANT_UIf8(1)。

Length

Length 项给出了后续 bytes 项的长度(字节数)。

Bytes

Bytes 项中包含按照变体 UTF-8 格式存储的字符串中的字符。


  • 从 u0001 到 u007f 的所有字符(除空字符以外的所有 ASCII 字符)都使用一个字节表示。

  • 空字符 null(u0000’)和从 u0080 到 u07f 的所有字符使用两个字节表示

  • 从 u0800'到\ufff’的所行字符使用三个字节表示。



CONSTANT_Long_info 表

固定长度的 CONSTANT_Long_info 表用于存储 long 类型常量。该表仅存储 long 类型值而不存储符号引用。下面列出了 CONSTANT_Long_info 表的格式。

CONSTANT_Long_info 表的格式


正如之前所述,常量池中的long类型值(64bit,一个条目项代表 32bit)占用两个条目,并且其下一个条目的索引值应比其当前条目的索引值大 2。在CONSTANT_Long_info结构中,存储了以下信息:


  • tag字段设置为CONSTANT_Long(5)。

  • bytes字段以大端格式存储long值。


因此,在一个 class 文件中,long值使用CONSTANT_Long_info结构表示,并占用常量池中的两个条目,其下一个条目的索引值比当前条目的索引值大 2。

CONSTANT_Double_info 表

"CONSTANT_Double_info"表是一种用于存储"double"类型常量的固定长度表格。与存储符号引用不同的是,该表格仅用于存储 double 值。

CONSTANT_Double_info 表的格式


以下是优化后的文本,供您参考:


如前所述,一个 double 类型的值会占据常量池中两个位置。


在 class 文件中,double 类型的入口后面紧跟着下一个入口,而下一个入口的索引值比前一个入口的索引值大 2。在 CONSTANT_Double_info 表中,各项内容如下:


  • tag 项的值为 CONSTANT_Double(6)。

  • bytes 项中按高位在前的格式存储 double 类型的值。



CONSTANT_Class_info 表

固定长度的 CONSTANT_Class_info 表使用符号引用来表述类或者接口。无论指向类、接口、字段, 还是方法, 所有的符号引用都包含一个 CONSTANT_Class_info 表。

CONSTANT_Class_info 表的格式


  • tag: tag 项的值为 CONSTANT_Class(7)。

  • name_index: name_index 项是一个指向 CONSTANT_Utf8info 表的索引,该表中包含了类或接口的全限定名。在 Java 中,数组也是对象,因此 CONSTANT_Class_info 表也可以用来描述数组类。

  • 数组描述符: name_index 项指向的 CONSTANT_Uf8_info 表中包含了数组的描述符,描述符可以作为数组类的名称。例如,一个 double[]数组类型的类名为它的描述符[D;一个 net.jini.core.lookup.ServiceItem[]数组类型的类名为它的描述符[Lnet/jini/core/lookup/ServiceItem;。


注意,Java 数组的维度最多为 255,因此数组描述符中的引导符“[”最多为 255 个。



CONSTANT_String_info

尚定长度的 CONSTANT_String_info 表用于存储文字字符串值,这些值可以表示为 java.lang.String 类的实例。该表仅存储文字字符串值,不存储符号引用。


CONSTANT_String_info {    u1 tag;    u2 string_index;}
复制代码


下面是 CONSTANT_String_info 表的格式:



  • tag: 表示标签,值为 CONSTANT_String(8)。

  • string_index: 是一个指向 CONSTANT_Utf8_info 表的索引,该表中存储了实际的字符串值。通过使用这样的表形式,可以方便地存储和引用字符串值,保证了程序的灵活性和可读性。



CONSTANT_Fieldref_info

固定长度的 CONSTANT_Fieldref_info 表用于描述指向字段的符号引用。


CONSTANT_Fieldref_info {    u1 tag;    u2 class_index;    u2 name_and_type_index;}
复制代码


下面是 CONSTANT_Fieldref_info 表的格式:



  • tag: 表示标签,值为 CONSTANT_Fieldref(9)。

  • class_index: 是一个指向 CONSTANT_Class_info 表的索引,该表中存储了字段所属的类或接口。

  • name_and_type_index: 是一个指向 CONSTANT_NameAndType_info 表的索引,该表中存储了字段的名称和描述符。

注意事项

class_index 所指向的 CONSTANT_Class_info 不仅代表类,还可能代表接口。虽然接口可以声明字段,并且可以将其声明为公共、静态和 final 类型,但如前所述,如果其他类使用编译时常量来初始化这些静态 final 字段,那么 class 文件不会包含对这些字段的符号引用。然而,class 文件可以包含对这些静态 final 字段常量值的副本。


例如,如果一个类使用在接口中声明的 float 类型的静态 final 字段,并且它被初始化为编译时的常量值,那么该类将在自己的常量池中具有一个 CONSTANT_Float_info 表来存储这个 float 值的副本。


如果该接口使用在运行时才能计算出的表达式来初始化它的静态 final 字段,那么在使用该字段的类的常量池中,将会有一个对该接口中字段的符号引用的 CONSTANT_Fieldref_info 表。



CONSTANT_Method ref_into 表

以下是对固定长度的 CONSTANT_Methodref_info 表使用符号引用来表示类中声明的方法(不包括接口中的方法)进行优化和润色后的描述:固定长度的 CONSTANT_Methodref_info 表使用符号引用来表示类中声明的方法(不包括接口中的方法)。


下面是 CONSTANT_Methodref_info 表的格式:



  • tag(标签):tag 项的值为 CONSTANT_Methodref (10)。

  • class_index(类索引):class_index 项给出了声明了被引用方法的类的 CONSTANT_Class_info 表的索引。class_index 所指定的 CONSTANT_Class_info 表必须表示一个类,而不能是接口。指向接口中声明的方法的符号引用应使用 CONSTANT_InterfaceMethodref 表。

  • name_and_type_index(名称和类型索引):name_and_type_index 提供了 CONSTANT_NameAndType_info 表的索引,该表提供了方法的简单名称和描述符。如果方法的简单名称以"<"(\u003c)符号开头,则该方法必须是一个实例化方法。它的简单名称应为"<init>",并且返回类型必须为 void。否则,该方法应该是一个常规方法。



CONSTANT_Interface Method ref_info 表

固定长度的 CONSTANT_InterfaceMethodref_info 表使用符号引用来描述接口中声明的方法,而不包括类中的方法。下面是 CONSTANT_InterfaceMethodref_info 表的格式:

CONSTANT_InterfaceMethodref_info 格式

CONSTANT_InterfaceMethodref_info {    u1 tag;    u2 class_index;    u2 name_and_type_index;}
复制代码


  • tag(标签):tag 字段的值为 CONSTANT_InterfaceMethodref(11)。

  • class_index(类索引):class_index 字段给出了声明了被引用方法的接口的 CONSTANT_Class_info 表的索引。由 class_index 指定的 CONSTANT_Class_info 表必须表示一个接口,而不能表示一个类。对于在类中声明的方法的符号引用,应使用 CONSTANT_Methodref 表。

  • name_and_type_index(名称和类型索引):name_and_type_index 字段提供了 CONSTANT_NameAndType_info 表的索引,该表提供了方法的简单名称和描述符。


通过这种固定长度的格式,CONSTANT_InterfaceMethodref_info 表可以准确地描述接口中声明的方法,并通过符号引用的方式实现对这些方法的引用。



CONSTANT_Name And Type_info 表

固定长度的 CONSTANT_Namc And Type_info 表构成指向字段或者方法的符号引用的一部分。该表提供了所引用字段或者方法的简单名称和描述符的常量池入口。

CONSTANT_Name_And_Type_info 表的格式

CONSTANT_Name_And_Type_info {    u1 tag;    u2 name_index;    u2 description_index;}
复制代码


CONSTANT_NameAndType_info 表的各个项如下:


  • tag:tag 项的值为 CONSTANT_NameAndType(12),表示该项为 CONSTANT_NameAndType_info 的标识。

  • name_index:name_index 项给出了 CONSTANT_Utf8_info 表的索引,该表包含字段或方法的名称。这个名称必须是有效的 Java 编程语言标识符,或者是"<init>"。

  • descriptor_index:descriptor_index 项提供了 CONSTANT_Utf8_info 表的索引,该表包含字段或方法的描述符。这个描述符必须是有效的字段或方法描述符。



总结说明

此章节主要介绍了对于 Class 字节码文件内部的技术核心结构剖析属性的定义,下一节会对于底层存储结构进行分析说明。敬请期待:《深入浅出 Java 虚拟机 — JVM 原理与实战》带你攻克技术盲区,夯实底层基础 —— 吃透 class 字节码文件技术基底和实现原理(底层结构剖析)



彩蛋介绍

在这里,我向大家推荐一本关于 JVM 优化和调优的实战系列书籍,《深入浅出 Java 虚拟机 — JVM 原理与实战》。这本书是最新出版的,内容涵盖了与我们当前工作和开发实例密切相关的技术和实战案例。通过学习这本书,我们可以深入了解 Java 虚拟机的原理,并通过实践掌握优化和调优的技巧。我诚挚地推荐这本书给大家,相信它将为我们的工作和技术发展带来巨大的收益。希望大家能够抽出时间多多学习一下这本宝贵的资料


当当-点击链接】【京东-点击链接

发布于: 刚刚阅读数: 3
用户头像

洛神灬殇

关注

🏆 InfoQ写作平台-签约作者 🏆 2020-03-25 加入

【个人简介】酷爱计算机科学、醉心编程技术、喜爱健身运动、热衷悬疑推理的“极客达人” 【技术格言】任何足够先进的技术都与魔法无异 【技术范畴】Java领域、Spring生态、MySQL专项、微服务/分布式体系和算法设计等

评论

发布
暂无评论
《深入浅出Java虚拟机 — JVM原理与实战》带你攻克技术盲区,夯实底层基础 —— 吃透class字节码文件技术基底和实现原理(核心结构剖析)_Java_洛神灬殇_InfoQ写作社区