深入理解JVM - 类文件结构(下)

简介: 深入理解JVM - 类文件结构(下)

常量池



注意:常量池的入口需要放置一个U2(16进制的F)类型的数据,用于记录常量池的容器计量值

主次版本号之后就是常量池的内容了,可以直接看做是class文件的资源仓库,同时是class文件结构关联最多的数据部分,也是最大的数据项之一。光靠这一篇文章肯定是无法讲完的,同样即使讲完了也记不住,所以这一部分我们只需要掌握存放的内容即可。


存放内容


常量池中主要存放两大类常量:字面量(Literal)符号引用(Symbolic References)

字面量:比如文本字符串,final常量

符号引用则存放如下内容:

  • 模块导出或者开放包
  • 类与接口全限定名称
  • 字段与描述符
  • 方法句柄和类型
  • 动态调用点和动态常量


常量表



常量池的每一个常量都是一个表,最初只有11种结构,后来扩展出4种和动态语言相关的常量,为了模块化:加入CONSTANT_Module_info和CONSTANT_Package_info两个常量,最终就是11+4+2 = 17种常量,所以到JDK16版本为止有17种常量。同意,记是记不住的,我们也不需要记住:


网络异常,图片无法展示
|

上面的表重点关注:CONSTANT_Dynamic_infoCONSTANT_InvokeDynamic_info


下面我们挑重点看一下这些常量对于JDK的影响。


Constant_class_info 类型


CONSTANT_Class_info类型,此类型的常量代表一个类或者接口的符号引用,主要结构为一个U1类型的tag(标志位)和u2类型的name_index(名称引用)。

tag的作用是标志位区分常量的类型,而name_index表示常量池的索引值。

U1代表一个字节:1111,U2代表两个字节:11111111,也就是65535。

后续以此类推,不再赘述。

在讲下一个类型之前,我们先提一个问题:**JAVA方法最大长度是多少,为什么?**我们都知道方法起名是有上限的,但是究竟的上限是多少,这里我们根据class的文件结构来进行解读:

CONSTANT_Utf8_info类型


CONSTANT_Utf8_info类型常量,此常量代表了这个类(或者接口)的全限定名

CONSTANT_Utf8_info类型常量指向name_index,0x00002常量池第二项常量,标记为0x01

。接下来就是重点了,CONSTANT_Utf8_info型常量的最大长度也就是Java中方法、字段名的最大长度。而这里的最大长度就是length的最大值,既u2类型能表达的最大值65535。所以Java程序中如果定义了超过64KB英文字符的变量或方法名,即使规则和全部字符都是合法的,也会无法编译

书中提到的常量也是这两种,如果我们想要查看字节码,可以使用javap指令。

JDK 7时增加了前三种:CONSTANT_MethodHandle_info、CONSTANT_MethodType_info和 CONSTANT_InvokeDynamic_info。DK 11中又增加了第四种常量CONSTANT_Dynamic_info


访问标志


在常量池结束之后,用两个字节表示访问标志与接口的访问信息。里面的具体内容包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型;如果是类的话,是否被声明为final

access_flags(访问标志)中一共有16个标志位可以使用,当前只定义了其中9个,如果要计算它的值,可以使用0x0001|0x0020=0x0021


网络异常,图片无法展示
|


类索引、父类索引与接口索引集合


简单来说,Class文件中由这三项数据来确定该类型的继承关系。类索引用于确定这个类的全限定名,而父类索引中记录了当前类的父类的全限定名,需要注意的是因为JAVA的顶级父类永远是java.lang.Object,除了java.lang.Object外,所有Java类的父类索引都不为0,而接口索引集合就用来描述这个类实现了哪些接口。


字段表集合


字段表(field_info)用于描述接口或者类中声明的变量,注意字段包含了java当中的类变量或者实例级常量。这里可能有一个疑问,为什么方法中的局部变量不属于字段?**。

首先,我们需要注意的是,方法中的变量都是属于栈帧范围内的,所以方法中的变量生命范围逃不开一个栈帧的高度(或者说容量)。类中的字段可以定义是否公开,是否静态,引用是否不可修改等,但是局部变量做不到,所以字段表中无法存放局部变量,也没有必要存放。

同样的,上述这些信息中,各个修饰符都是布尔值,要么有某个修饰符,要么没有,很适合使用标志位来表示。

接下来书里面的内容就是讲述字段表集合的细节了,由于我们不需要去研究GC,所以这里感兴趣可以直接去看书里面的内容。


方法表集合


和字段表内容大致相同,Class文件存储格式中对方法的描述与对字段的描述采用了几乎完全一致的方式,方法表集合包括了:访问标志(access_flags)、名称索引(name_index)、描述符索引(descriptor_index)、属性表集合(attributes)。同时在标志部分增加了和方法相关的标志。

既然是方法表集合,那代码到哪去了?答案是在方法属性里面有一个code属性,这属性就是存放方法中代码的地方,也是对于程序员来说最有用的地方。这里需要注意需要注意的是如果父类方法在子类中没有被重写(Override),方法表集合中就不会出现来自父类的方法信息

同样,需要注意的是虽然java语法不支持返回值重载但是在class特征签名返回值不同也可以同时存在。

需要注意的是volatile关键字和transient关键字在方法表集合当中不一样,


属性表集合(核心)


属性表的内容主要是搭配前面所讲的字段和方法进行搭配的,最初的预定义属性最初只有9种,最新的《Java虚拟机规范》的Java SE 12版本中,现在已经有了29种。

属性结构的内容如下:
- u2 attrcibute_name_index
- U4 attribute_length
- U1 info
复制代码


Code属性


Java程序方法体里面的代码经过Javac编译器处理之后,最终变为字节码指令存储在Code属性内。但是需要注意并不是所有的方法都要有这个属性,因为方法的内容是可以为空的。

Code属性是Class文件中最重要的一个属性,如果把一个Java程序中的信息分为代码(Code,方法 体里面的Java代码)和元数据(Metadata,包括类、字段、方法定义及其他信息)两部分,那么在整 个Class文件里,Code属性用于描述代码,所有的其他数据项目都用于描述元数据

后续的内容是根据的一个javap生产的字节码指令来进行相关的解读,这些内容也是不是关键的内容,为了减轻记忆负担,本文也不做过多介绍。


异常表


Code数量里面还包含一个异常表,异常表也是JAVA代码的一部分,这部分内容虽然可以通过GOTO这种跳转指令实现,但是在JVM规范中是强制规范JAVA语言使用异常表而不是GOTO指令实现JAVA的异常以及Finally的处理机制。

为什么不能用GOTO?这就要问问C语言这个老先生了,虽然很多语言都保留了GOTO的语法,但是无一例外没有人推荐使用。因为它不仅容易出BUG,并且写出来的源代码十分难以理解。


line_number_table 属性


  • 用于描述行号和字节码行号对应关系
  • 用于debug使用
  • -line_number_info 中两个u2类型的数据项
  • start_pc: 字节码行号
  • line_number java源代码行号

由于篇幅问题,其他的属性这里就不过多介绍了,思维导图摘录了大致内容,在遇到疑问的时候翻一翻记忆会比较深,这里也不做过多叙述。


总结


class文件结构靠着死记硬背是记不住的,需要根据JVM的特性来进行理解,比如动态语言是如何实现的,再比如DEBUG是如何实现的等等,用这些内容帮助理解记忆。

从本文也可以看到,其实重点都在属性表集合这一部分。所以如果需要重点理解JAVA的特性,可以从这个属性表开始。


写在最后


不管看几次,我也记不住,哎......

相关文章
|
2月前
|
安全 Java 应用服务中间件
JVM常见面试题(三):类加载器,双亲委派模型,类装载的执行过程
什么是类加载器,类加载器有哪些;什么是双亲委派模型,JVM为什么采用双亲委派机制,打破双亲委派机制;类装载的执行过程
JVM常见面试题(三):类加载器,双亲委派模型,类装载的执行过程
|
25天前
|
存储 算法 Java
聊聊jvm的内存结构, 以及各种结构的作用
【10月更文挑战第27天】JVM(Java虚拟机)的内存结构主要包括程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区和运行时常量池。各部分协同工作,为Java程序提供高效稳定的内存管理和运行环境,确保程序的正常执行、数据存储和资源利用。
46 10
|
1月前
|
SQL 缓存 Java
JVM知识体系学习三:class文件初始化过程、硬件层数据一致性(硬件层)、缓存行、指令乱序执行问题、如何保证不乱序(volatile等)
这篇文章详细介绍了JVM中类文件的初始化过程、硬件层面的数据一致性问题、缓存行和伪共享、指令乱序执行问题,以及如何通过`volatile`关键字和`synchronized`关键字来保证数据的有序性和可见性。
29 3
|
1月前
|
缓存 前端开发 Java
JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制
这篇文章详细介绍了JVM中ClassLoader的工作原理,包括类加载器的层次结构、双亲委派机制、类加载过程、自定义类加载器的实现,以及如何打破双亲委派机制来实现热部署等功能。
43 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 的使用
|
3月前
|
存储 算法 Java
JVM组成结构详解:类加载、运行时数据区、执行引擎与垃圾收集器的协同工作
【8月更文挑战第25天】Java虚拟机(JVM)是Java平台的核心,它使Java程序能在任何支持JVM的平台上运行。JVM包含复杂的结构,如类加载子系统、运行时数据区、执行引擎、本地库接口和垃圾收集器。例如,当运行含有第三方库的程序时,类加载子系统会加载必要的.class文件;运行时数据区管理程序数据,如对象实例存储在堆中;执行引擎执行字节码;本地库接口允许Java调用本地应用程序;垃圾收集器则负责清理不再使用的对象,防止内存泄漏。这些组件协同工作,确保了Java程序的高效运行。
27 3
|
3月前
|
C# UED 开发者
WPF动画大揭秘:掌握动画技巧,让你的界面动起来,告别枯燥与乏味!
【8月更文挑战第31天】在WPF应用开发中,动画能显著提升用户体验,使其更加生动有趣。本文将介绍WPF动画的基础知识和实现方法,包括平移、缩放、旋转等常见类型,并通过示例代码展示如何使用`DoubleAnimation`创建平移动画。此外,还将介绍动画触发器的使用,帮助开发者更好地控制动画效果,提升应用的吸引力。
176 0
|
3月前
|
存储 监控 算法
深入解析JVM内部结构及GC机制的实战应用
深入解析JVM内部结构及GC机制的实战应用
|
1月前
|
存储 安全 Java
jvm 锁的 膨胀过程?锁内存怎么变化的
【10月更文挑战第3天】在Java虚拟机(JVM)中,`synchronized`关键字用于实现同步,确保多个线程在访问共享资源时的一致性和线程安全。JVM对`synchronized`进行了优化,以适应不同的竞争场景,这种优化主要体现在锁的膨胀过程,即从偏向锁到轻量级锁,再到重量级锁的转变。下面我们将详细介绍这一过程以及锁在内存中的变化。
37 4
|
8天前
|
Arthas 监控 Java
JVM进阶调优系列(9)大厂面试官:内存溢出几种?能否现场演示一下?| 面试就那点事
本文介绍了JVM内存溢出(OOM)的四种类型:堆内存、栈内存、元数据区和直接内存溢出。每种类型通过示例代码演示了如何触发OOM,并分析了其原因。文章还提供了如何使用JVM命令工具(如jmap、jhat、GCeasy、Arthas等)分析和定位内存溢出问题的方法。最后,强调了合理设置JVM参数和及时回收内存的重要性。