JVM之Class结构属性表

简介: JVM之Class结构属性表

JVM之Class结构属性表

概述

上篇文章提到过在Class结构表中,属性表存在于Class表,字段表和方法表中,是为了描述额外的信息

属性表在《JAVA虚拟机规范》中并没有像其他数据一样做严格的限制,我们甚至可以自己实现一个编译器往Class结构的属性表中注入额外的属性信息,虚拟机运行时会忽略掉它识别不了的属性。

属性表属性总览

这张图中按Class结构,字段表,方法表这三个维度进行了区分标注,将三者共有的属性提取到最顶部的黄色椭圆中,蓝色代表各自属性表额外用到的属性红色代表Code属性中引用的其他属性的集合

引自《深入理解JAVA虚拟机》,读者也可以看这下面的两张图:

属性结构

首先表结构中的前两个字节说明该属性是叫什么名字,也就是什么类型的属性,最终指向常量池中的CONSTANT_Utf8_info类型的常量。

eg:Code,ConstantValue,SourceFile。。。等

接着用四个字节描述属性值的长度,也就是说明属性值所占用的字节数;

除了第一个属性名称信息和该属性描述的属性长度,其他的就是属性值了,因此该值(属性值长度)固定为 整个属性表长度-6个字节

最后列出属性信息,有多少属性信息呢?前四个字节已经列出了这个属性包含多少个属性,因此最后描述各个属性的信息。

前两个属性是所有属性都共有的,之后就不进行讲解了。

常见属性

Code

只有方法内有方法体的方法表才会有这项属性,像抽象方法,非defult接口方法是没有这个属性的(没有方法体)


该属性用于存放
操作数栈最大深度
本地变量表最大占用存储空间
方法中Catch块定义的异常类型和数量
编译后方法体的字节码指令和指令长度
还有其他属性


首先先看下Code属性表的结构:


1.max_stack操作数栈最大深度

先看下之前的这篇文章,Java程序运行是基于栈的操作,就是说的该项属性。

通过指令从本地变量表中拿数据放到栈顶;将数据从栈顶保存到本地变量中;对栈顶两个值进行运算后将结果压入栈顶;将栈顶的值作为返回结果return…等等这些都是通过操作数据结构栈来完成的。

这项属性描述了方法中操作数栈的最大深度,虚拟机运行时会根据这项属性来分配栈帧中操作数栈的最大深度。


2.max_locals局部变量表所需空间

局部变量表最大占用的存储空间,存储的单位用的是Slot(变量槽),一个

Slot占用空间大小为32位。

存储范围:

比如方法的参数,Catch块中定义的异常类型,方法体中定义的局部变量。

对于方法参数来说,根据实例方法(通过对象才能访问)和静态方法(通过类名就可以直接访问)又有不同;实例方法的参数中第一个是this,该变量会在编译时加入,而静态方法则没有这个变量。

存储空间:

对于小于32位的数据类型用一个变量槽,大于32位的用多个变量槽存储(比如小于64位的数据类型double和long用两个变量槽存储)。

优化

对变量限定作用域,如果变量超出了作用域范围,那么存储该变量的Slot进行存储其他的变量,也就是重用变量槽。

根据同时生存的最大局部变量数量和类型计算出max_locals的大小。


3.code_length,code属性用于表述方法体编译后的字节码指令长度和字节码指令流

字节码指令流,一个字节代表一条指令,即每条指令就是一个u1类型的单字节;u1数据类型取值范围是0X00~0XFF,对应十进制为0到255。

即u1可以表达256条指令,目前使用的大约只有200条

方法体中不允许超过65535条字节码指令,如果超出了编译器拒绝编译;但是code_length用的是u4长度值,可以达到2的32次幂,相当于指令长度只用了一半(u2长度)。


4.exception_table_length,exception_table用于描述方法块中Catch块定义的异常数量和类型

如果方法体内部没有catch块则length为0,后面的字节不描述exception_table

Exception table:

from to target type

0 5 10 Class java/lang/Exception

0 5 21 any

10 16 21 any


Exceptions

该属性和上面提到的exception_table描述的信息不一样,exception_table是描述方法体中Catch块中定义的异常数量和类型;而该项属性是描述方法throws的异常数量和类型,通俗来说就是调用该方法需要catch的异常,也叫作受查异常。

因此上面的excepitons_table描述方法体内部Catch的异常,Exceptions描述方法抛出的异常

结构


LineNumberTable

用于描述Java代码行号和字节码行号的对应关系,该项属性可选择是否输出

用处:当执行某段字节码指令抛出异常时可以根据这个对应关系,提示开发者Java代码对应的行号。

结构


LocalVariableTable,LocalVariableTypeTable

1.LocalVariableTable

用于描述局部变量表中的变量与JAVA代码中定义变量之间的关系,同样也可以选择不生成该属性

用处:当编写代码引用到这个方法时,参数可以直接显示变量名和类型,如果没有该属性,就用arg0,arg1代替;调试信息的时候可以根据参数变量名明确语义。

2.LocalVariableTypeTable

引入泛型后加入的属性,结构和LocalVariableTable相似,将原先用于描述字段描述符的descriptor_index替换成了描述字段的特征签名(Signature)。

对于非泛型变量,特征签名和描述符是一致的,但是对于泛型来说,由于泛型参数类型的擦除,描述符不能够描述泛型类型,因此定义了该属性来完成泛型的描述。

结构:


ConstantValue

变量初始化,赋值时机:

位于该属性结构中的常量将会在类加载的准备阶段就会初始化并且赋值

其他的静态变量在这个阶段只是会被初始化然后赋默认值,如果静态变量设置了final关键字,那么就是第一种情况会对变量进行赋值

对于实例变量(非静态变量)的赋值是在实例构造器《init》中。

该结构中存放的字段是:

《Java虚拟机规范》中规定该属性结构中存放的必须是静态的字段,而对于javac编译器来说还需要满足final关键字的修饰,因此经过javac编译器编译后的该属性中存放的字段必须是static并且是final的。

通知虚拟机自动为静态变量赋值(上面那句话)。该属性中只能存放基本类型和String,因为该属性的属性值

结构:


Deprecated及Synthetic属性

这两项属性有点特殊,不携带任何属性值,出现这两个属性的目的只是为了标识,这两个属性只有存在或不存在。

1.Deprecated属性

该属性用于表示某个类,字段或方法已经不再推荐使用,通过“@deprecated”注解设置这个属性

2.Synthetic属性

该属性用于表示字段或者方法是编译器自己添加的,不是代码中的。

也可以通过设置访问标志ACC_SYNTHETIC标志位生成该项属性。

结构:

两者都一样,不携带任何属性值。只是用于标识


StackMapTable

该属性位于Code属性的属性表中

用处:在类加载阶段的验证阶段使用该属性,代替以前耗性能的基于数据流分析的类型推导验证器(有了该属性之后就不用类型推导了,可以直接判断类型是不是符合要求,之后单独写类加载阶段进行分析)

之前验证阶段是基于数据流来进行分析推导出操作数栈和本地变量表操作的类型是否一致等(比如istore需要将操作数栈的数据保存到本地变量表中,但是取出的数据类型不是int就会发生问题),现在基于该项属性可以不用推导

结构:

一个Code属性最多只能有一个StackMapTable属性


MethodParameters

用于记录方法的各个形参名称和信息

方法参数属性,位于class中的属性表中。

之前说过这部分是存储在局部变量表中的,因为方法中有方法体code属性,而code中需要有局部变量表属性代表这个方法中的变量存储。

但是为什么还要单独抽出一个属性放在class中呢?

大家想想没有code就没有局部变量表,没有局部变量表是不是就不能存储方法参数了;你看接口中他有方法吧但是呢他其实没有方法提code所以它的方法参数往哪放呢?往他借口的属性表集合中放,也就是与code同级。这样的话我接口里可以直接获得方法参数通过这个属性;而对于正常的方法也就是有方法体的代码可以从code中的局部变量表中拿。

数据结构:

1.首先说明他是什么,我是一个方法参数类型

2.我说明我存储的时候数据有多长(多少字节),为了切割按照这个就可以正确读取对应的数据;但是如果这个属性中还用到了其他的数据结构(属性),那么就是这个属性的个数了

3.对于没有再次用到其他属性来描述的属性直接使用定长数据即可;但是对于有用到其他属性来描述这个属性的话,则后面是对应的属性一个一个排开,然后每个属性如果是定长的话则不需要通过长度来说明所占字节,然后这个属性中存储的第一个永远是他是什么也就是名字,然后再是对应的值

不断使用这种结构来描述一个完整的class结构

结构:


目录
相关文章
|
23天前
|
存储 算法 Java
聊聊jvm的内存结构, 以及各种结构的作用
【10月更文挑战第27天】JVM(Java虚拟机)的内存结构主要包括程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区和运行时常量池。各部分协同工作,为Java程序提供高效稳定的内存管理和运行环境,确保程序的正常执行、数据存储和资源利用。
45 10
|
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 的使用
|
3月前
|
存储 算法 Java
JVM组成结构详解:类加载、运行时数据区、执行引擎与垃圾收集器的协同工作
【8月更文挑战第25天】Java虚拟机(JVM)是Java平台的核心,它使Java程序能在任何支持JVM的平台上运行。JVM包含复杂的结构,如类加载子系统、运行时数据区、执行引擎、本地库接口和垃圾收集器。例如,当运行含有第三方库的程序时,类加载子系统会加载必要的.class文件;运行时数据区管理程序数据,如对象实例存储在堆中;执行引擎执行字节码;本地库接口允许Java调用本地应用程序;垃圾收集器则负责清理不再使用的对象,防止内存泄漏。这些组件协同工作,确保了Java程序的高效运行。
27 3
|
3月前
|
存储 安全 Java
JVM内存结构
这篇文章详细介绍了Java虚拟机(JVM)的内存结构,包括类的加载过程、类加载器的双亲委派机制、沙箱安全机制、程序计数器、Java栈、Java堆、本地方法和本地方法栈等关键组件及其作用。
JVM内存结构
|
3月前
|
C# UED 开发者
WPF动画大揭秘:掌握动画技巧,让你的界面动起来,告别枯燥与乏味!
【8月更文挑战第31天】在WPF应用开发中,动画能显著提升用户体验,使其更加生动有趣。本文将介绍WPF动画的基础知识和实现方法,包括平移、缩放、旋转等常见类型,并通过示例代码展示如何使用`DoubleAnimation`创建平移动画。此外,还将介绍动画触发器的使用,帮助开发者更好地控制动画效果,提升应用的吸引力。
167 0
|
3月前
|
存储 监控 算法
深入解析JVM内部结构及GC机制的实战应用
深入解析JVM内部结构及GC机制的实战应用
|
4月前
|
存储 运维 Java
Java面试题:JVM的内存结构有哪些主要部分?请简述每个部分的作用
Java面试题:JVM的内存结构有哪些主要部分?请简述每个部分的作用
58 9
|
4月前
|
存储 设计模式 监控
Java面试题:简述JVM的内存结构,包括堆、栈、方法区等。栈内存优化的方法有 哪些?
Java面试题:简述JVM的内存结构,包括堆、栈、方法区等。栈内存优化的方法有 哪些?
47 0
|
1月前
|
存储 安全 Java
jvm 锁的 膨胀过程?锁内存怎么变化的
【10月更文挑战第3天】在Java虚拟机(JVM)中,`synchronized`关键字用于实现同步,确保多个线程在访问共享资源时的一致性和线程安全。JVM对`synchronized`进行了优化,以适应不同的竞争场景,这种优化主要体现在锁的膨胀过程,即从偏向锁到轻量级锁,再到重量级锁的转变。下面我们将详细介绍这一过程以及锁在内存中的变化。
37 4