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

本文主要是介绍《深入浅出Java虚拟机 — JVM原理与实战》带你攻克技术盲区,夯实底层基础 —— 吃透class字节码文件技术基底和实现原理(核心结构剖析),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

带你攻克技术盲区,夯实底层基础 —— 吃透class字节码文件技术基底和实现原理(核心结构剖析)

  • 特殊字符串
    • 全限定名
    • 简单名称
    • 描述符
      • 基本类型终结符
        • 字段描述符示例
        • 方法描述符示例
  • 常量池
    • CONSTANT_Utf8_info表
      • CONSTANT_Utf8_info表的基本类型信息
        • 属性相关的字符串
          • Tag
          • Length
          • Bytes
    • CONSTANT_Long_info表
      • CONSTANT_Long_info表的格式
    • CONSTANT_Double_info表
      • CONSTANT_Double_info表的格式
    • CONSTANT_Class_info表
        • CONSTANT_Class_info表的格式
    • CONSTANT_String_info
    • CONSTANT_Fieldref_info
      • 注意事项
    • CONSTANT_Method ref_into表
    • CONSTANT_Interface Method ref_info表
      • CONSTANT_InterfaceMethodref_info格式
    • CONSTANT_Name And Type_info表
      • CONSTANT_Name_And_Type_info表的格式
  • 总结说明
  • 彩蛋介绍

特殊字符串

常量池中包含了符号引用,其中包括三种特殊的字符吊:全限定名简单名称描述符
在这里插入图片描述
所有的符号引用都包括类或接口的全限定名

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

特殊字符串在符号引用中的使用也用于描述被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:FieldType
ComponentType:FieldType
FieldType:Base TypeObjectTypeArray Type
BaseType:BCDEIJSz
ObjectType:L<classname>;
ArrayType:[ ComponentType
MethodDescriptor:(ParameterDescriptor) *ReturnDescriptor
ParameterDescriptor:FieldType
ReturnDescriptor:FieldTypeV

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

基本类型终结符

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

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

终结符类型说明
Bbyte字节类型
Cchar字符类型
Ddouble双精度浮点数类型
Ffloat单精度浮点数类型
Iint整数类型
Jlong长整数类型
Sshort短整数类型
Zboolean布尔类型

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

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

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

字段描述符示例
描述符字段声明
Iint I
[[Jlong[][] windingRoad
[Ljava/lang/Objectjava.lang.Object[]
Ljava/util/Hashtablejava.util.Hashtable ht
[[[Zboolean[][][] isReady
方法描述符示例

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

描述符方法声明
()Iint getSize()
()Ljava/lang/String;String toString()
([Ljava/lang/String;)Vvoid main(String[] args)
()Vvoid wait()
(JI)Vvoid wait(long timeout, int nanos)
(ZILjava/lang/String;II)Zboolean regionMatches(boolean ignoreCase, int toOffset, String other, int offset, int len)
(IBI)Iint read(byte[] b, int off, int len)

常量池

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

类型名称数量
u1tag1
u1info根据tag值决定

在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的格式。

类型名称数量
ultag1
u2length1
ulbyteslength

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表的格式

类型名称数量
ultag1
u8bytes1

正如之前所述,常量池中的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表的格式

类型名称数量
ultag1
u8bytes1

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

如前所述,一个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表的格式
类型名称数量
ultag1
u2name_index1
  • 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表的格式:

类型名称数量
ultag1
u2string_index1
  • 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表的格式:

类型名称数量
ultag1
u2class_index1
u2name_and_type_index1
  • 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表的格式:

类型名称数量
ultag1
u2class_index1
u2name_and_type_index1
  • 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)符号开头,则该方法必须是一个实例化方法。它的简单名称应为”",并且返回类型必须为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编程语言标识符,或者是""。

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


总结说明

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


彩蛋介绍

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

这篇关于《深入浅出Java虚拟机 — JVM原理与实战》带你攻克技术盲区,夯实底层基础 —— 吃透class字节码文件技术基底和实现原理(核心结构剖析)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/658258

相关文章

Java实现检查多个时间段是否有重合

《Java实现检查多个时间段是否有重合》这篇文章主要为大家详细介绍了如何使用Java实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

Java判断多个时间段是否重合的方法小结

《Java判断多个时间段是否重合的方法小结》这篇文章主要为大家详细介绍了Java中判断多个时间段是否重合的方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录判断多个时间段是否有间隔判断时间段集合是否与某时间段重合判断多个时间段是否有间隔实体类内容public class D

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

IDEA编译报错“java: 常量字符串过长”的原因及解决方法

《IDEA编译报错“java:常量字符串过长”的原因及解决方法》今天在开发过程中,由于尝试将一个文件的Base64字符串设置为常量,结果导致IDEA编译的时候出现了如下报错java:常量字符串过长,... 目录一、问题描述二、问题原因2.1 理论角度2.2 源码角度三、解决方案解决方案①:StringBui

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

Java中ArrayList和LinkedList有什么区别举例详解

《Java中ArrayList和LinkedList有什么区别举例详解》:本文主要介绍Java中ArrayList和LinkedList区别的相关资料,包括数据结构特性、核心操作性能、内存与GC影... 目录一、底层数据结构二、核心操作性能对比三、内存与 GC 影响四、扩容机制五、线程安全与并发方案六、工程

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

Java调用DeepSeek API的最佳实践及详细代码示例

《Java调用DeepSeekAPI的最佳实践及详细代码示例》:本文主要介绍如何使用Java调用DeepSeekAPI,包括获取API密钥、添加HTTP客户端依赖、创建HTTP请求、处理响应、... 目录1. 获取API密钥2. 添加HTTP客户端依赖3. 创建HTTP请求4. 处理响应5. 错误处理6.