JVM(五),linux云计算集群架构师

这种常量结构代表一个int值,组成如下

  • tag:类型为u1,标志位

  • bytes:类型为u4,按照高位在前存储的int值,这里u4是4个字节,所以int占用4个字节

CONSTANT_Float_info

这种常量结构代表一个float值,组成如下

  • tag:标志位

  • bytes:类型为u4,按照高位在前存储的float值,所以float占用4个字节

CONSTANT_Long_info

这种常量结构代表一个long值,组成如下

  • tag:标志位

  • bytes:类型为u8,按照高位在前存储的long值,所以long占用8个字节

CONSTANT_Double_info

这种常量结构代表一个double值,组成如下

  • tag:标志位

  • bytes:类型为u8,按照高位在前存储的double值,所以double占用8个字节

CONSTANT_String_info

这种常量结构代表一个字符串,这也是一个索引,指向了CONSTANT_Utf8_info的

  • tag:标志位

  • index:u2类型,指向字符串字面量的索引

CONSTANT_Fieldref_info

这种常量结构用来形容字段的,代表字段的信息,比如字段来自哪个类,名称和类型是什么

  • tag:标志位

  • index:u2类型,指向声明字段的类或者接口描述符的CONSTANT_Class_info(然后CONSTANT_Class_info又指向一个CONSTANT_Utf8_info,CONSTANT_Utf8_info存储的是符号索引)

  • index:u2类型,指向描述字段的CONSTANT_NameAndType_info

CONSTANT_Methodref_info

这种常量结构用来形容方法的,代表方法的信息,比如方法来自哪个类,名称和返回值类型是什么

  • tag:标志位

  • index:u2类型,指向声明方法的类描述符的CONSTANT_Class_info

  • index:u2类型,指向描述方法的CONSTANT_NameAndType_info

CONSTANT_InterfaceMethod-ref_info

这种常量结构也是用来形容方法的,只不过代表的是接口中的放啊

  • tag:标志位

  • index:u2类型,指向声明方法的接口描述符CONSTANT_Class_info

  • index:u类型,指向描述方法的CONSTANT_NameAndType_info

CONSTANT_NameAndType_info

这种 结构是用来形容字段或者方法的名称常量和描述符常量的

  • tag:标志位

  • index:u2类型,指向该字段或者方法名称常量项的索引

  • index:u2类型,指向该字段或者方法描述符的常量项的索引

CONSTANT_MethodHandle_info

这种结构是用来形容给方法句柄的

  • tag:标志位

  • reference_kind:该参数决定了方法句柄的类型,而句柄的类型决定了方法句柄的字节码行为

  • reference_index:常量池的一个有效索引(句柄的作用就是可以访问其他常量池)

CONSTANT_Module_info

这种结构是用来形容模块的,也就是import

  • tag:标志位

  • name_index:指向存储模块名字的CONSTANT_Utf8_info的指针,而CONSTANT_Utf8_info里面是符号引用

CONSTANT_Package_info

这种结构是形容本类的包名称的,对应的就是package

  • tag:标志位

  • name_index:指向存储包名称的CONSTANT_Utf8_info结构

访问标志

常量池之后紧接着的就是访问标志了,访问标志的作用是用于识别一些类或者接口层次的访问信息,比如这个Class是个类还是个接口;访问修饰符是什么;是否是抽象的;是不是被final修饰的

访问标志占用2个字节,对应Class里面的access_flags,类型为u2

具体的标志位如下

| 标志名称 | 标志值 | 含义 |

| — | — | — |

| ACC_PUBLIC | 0x0001 | 是否为Public |

| ACC_FINAL | 0x0010 | 是否被声明为final,只有类可以i设置,接口不可以设置 |

| ACC_SUPER | 0x0020 | 是否允许使用invokespecial字节码指令的新语义 |

| ACC_INTERFACE | 0x0200 | 是不是一i个接口 |

| ACC_ABSTRACT | 0x0400 | 是否为abstract类型,只有抽象类和接口这个标志位才会为true |

| ACC_SYNTHETIC | 0x1000 | 判断该类是否为用户代码产生,如果是的话就为0,不是的话就为1,也就是标识这个类并非由用户代码产生 |

| ACC_ANNOTATION | 0x2000 | 标识这是一个注解 |

| ACC_ENUM | 0x4000 | 标识这是一个枚举 |

| ACC_MODULE | 0x8000 | 标识这是一个模块 |

在access_flags中,其实总共有16个标志位可以使用,而直至JDK9当前只定义了9个,没有使用到的标志位要求一律为0

那么才两个字节的access_flags如何去代表那么多含义呢?

从上表中可以看到,标志名称的标志值并不是连续的,而是有一定距离的,并且该距离通常以位数来拉开,比如说我一个0x8421的access_flags,只可以代表其是一个模块、并且为abstract类型、允许使用invokespecial字节码指令、且为public

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

继续往后的部分就是this_class、super_class、interfaces_count和interfaces部分

  • this_class:u2类型,本类索引

  • super_class:u2类型,父类索引

  • interfaces:一组u2类型的数据的集合,接口索引集合

类索引的作用是用于确定这个类的全限定名,而父类索引则是确定这个类的父类的全限定名,由于Java对于类是不允许多继承的,所以父类索引只有一个,除了Object之外,其他的类都有父类,也就是说其他的类的父类索引都不可能为0,而接口索引则存储了该类实现了哪些接口,这些被实现的接口将会按从左到右的顺序排列在接口索引集合中

本类索引、父类索引、接口索引集合都按顺序接着访问标志进行排列

本类索引和父类索引都是u2类型的,里面存储的是指向CONSTANT_Class_info结构的索引,通过该索引是可以找到CONSTANT_Class_info,而CONSTANT_Class_info存储的是指向CONSTANT_utf8_info的索引,CONSTANT_utf8_info里面存储的正式类的全限定名称(一个符号引用)

而对于接口索引集合是分为两部分的

  • interfaces_count:一个u2类型,存储的是类实现接口的数量,称为接口计数器,同时也表示了下面索引表的容量

  • interfaces:u2类型集合,称为接口索引表,存储的也是一个指向CONSTANT_Class_info结构的索引,通过该索引是可以找到对应的CONSTANT_Class_info,而CONSTANT_Class_info存储的是指向CONSTANT_Utf8_info的索引,CONSTANT_Utf8_info里面存储的正是接口的全限定名

字段表集合

上面已经将魔数、Class文件版本、常量、访问标志和类索引、父类索引、接口索引集合排列好了,接下来到类里面自身拥有的字段了,也就是对应fields_count与fields

字段表用于描述接口或者类中声明的变量,对于字段这个概念,字段是包括类级变量和实例级变量(即静态变量和成员变量,静态变量属于类,而成员变量属于实例),但并不包括在方法内部声明的局部变量

字段可以包括下面的信息

  • 字段的作用域,即修饰符(public private protected)

  • 字段的数据类型

  • 字段的名称

  • 是实例变量还是类变量,即有没有被static关键字修饰

  • 是否可变,即有没有被final关键字修饰

  • 是否并发可见,是否强制从主存中读取,即是否被volatile关键字修饰

  • 可否进行序列化,即是否被transient修饰符修饰

对于字段的各种修饰符,对应的其实只是一个布尔值而已,有为1,没有为0,说白了其实各种修饰符对应的只是一个标志位,每个字段都拥有这个标志位;但对于字段中的名称、数据类型、是无法统一起来的,因此只能通过引用常量池的常量来进行描述

所以对于字段表,拥有以下属性

| 类型 | 名称 | 数量 |

| — | — | — |

| u2 | access_flags:字段的修饰符 | 1 |

| u2 | name_index:字段的名称,指向常量池的一个常量 | 1 |

| u2 | descriptor_index:字段的描述符,指向常量池的一个常量 | 1 |

| u2 | attributes_count:额外信息的数量 | 1 |

| attribute_info | attributes:额外信息 | attributes_count |

字段访问标志

对于字段访问标志,因为访问标志有限并且每个字段都有,所以采用标志位的形式,记录在access_flags中(这里的访问标志并不仅仅是权限符,还有静态statc、序列化transient、内存可见性volatile)

字段访问标志使用对应的标志值来表示

| 标志名称 | 标志值 | 含义 |

| — | — | — |

| ACC_PUBLIC | 0x0001 | 字段是否为public |

| ACC_PRIVATE | 0x0002 | 字段是否为private |

| ACC_PROTECTED | 0x0004 | 字段是否为protected |

| ACC_STATIC | 0x0008 | 字段是否为static |

| ACC_FINAL | 0x0010 | 字段是否为final |

| ACC_VOLIATILE | 0x0040 | 字段是否为volatile |

| ACC_SYTHETIC | 0x1000 | 字段是否由编译器自动产生(一般都是代码产生) |

| ACC_TRANSIENT | 0x0080 | 字段是否为transient |

| ACC_ENUM | 0x4000 | 字段是否为enum |

可以看到,这里的标志值也不是连续的,比如access_flags最终组成了0x4080,它只能是为enum类型并且被transient进行修饰,而不会产生其他歧义现象

而对于接口之中的字段,都会有ACC_PUBLIC、ACC_STATIC、ACC_FINAL标志

名称与类型常量池引用

紧接着下来就是name_index与descriptor_index,这两个都是对常量池的引用,分别代表着字段的简单名称以及字段和方法的描述符

简单名称与描述符

之前在常量池中也有提到过简单名称与描述符,但没有具体将其含义,下面就来认识一下这两个概念

**简单名称其实是指:字段名称和没有类型和参数修饰的方法名称;**而描述符则相对复杂一些,描述符是包含详细信息的,描述符的作用是描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值

对于数据类型(这里也包括返回值的显示),有基本数据类型、引用类型和数组类型,对于基本数据类型,描述符采用大写字符来表示(这里的基本数据类型包括void),而如果是引用类型(对象类型)则是以L加对象的全限定名来并表示,详情如下表所示

| 标识字符 | 含义 |

| — | — |

| B | byte类型 |

| C | char类型 |

| D | double类型 |

| F | float类型 |

| I | int类型 |

| J | long类型 |

| S | short类型 |

| Z | boolean类型 |

| V | void类型 |

| L | 对象类型,比如返回值为Integer,则为Ljava/lang/Integer |

而对于数组类型,每一维度都会使用一个前置的"["字符来表示,比如如果返回值为一个字符串的二维数组,那么标识字符则是[[Ljava/lang/String,但如果是一个整形数组,则是[[I

对于方法,则是按照先参数列表、后返回值的顺序描述,参数列表按照参数的严格顺序放在一组小括号内,比如无参的toString方法,则为()Ljava/lang/String;括号里面的是参数,L代表返回值为引用类型,后面代表引用类型的具体类型,比如一个int(double a),对应二点形式则为(D)I

现在已经知道了简单名称与描述符,简单名称与描述符都只是对应常量池里面的CONSTANT_Utf8_info结构而已,而这里的name_index和descriptor_index是一个指向常量池的简单名称与描述符的索引

字段表中包含的数据一般在descriptor_index就结束了,但在此之后是会跟随一个属性表集合,用来存储一些额外的信息,倘若直接给一个静态变量并且设置了初值,final static int a = 123,那么属性表中可能会存在一个ConstantValue属性,其值指向了常量表中的123

这里要注意,字段表集合中不会列出从父类或者接口那里继承而来的字段,但可能出现原本代码中不存在的字段,比如在内部类中为了保持对外部类的访问性,内部类中会自动添加指向外部类的字段

方法表集合

字段存放在字段表集合中,而方法存放在方法表集合中,并且方法表的描述跟字段表的描述是一样的

| 类型 | 名称 | 数量 |

| — | — | — |

| u2 | access_flags:方法的修饰符 | 1 |

| u2 | name_index:方法的名称,指向常量池的一个常量 | 1 |

| u2 | descriptor_index:方法的描述符,指向常量池的一个常量 | 1 |

| u2 | attributes_count:额外信息的数量 | 1 |

| attribute_info | attributes:额外信息 | attributes_count |

字段访问标志

方法表同样也有字段访问标志,不过字段访问标志跟字段表的有点出入

  • 字段表拥有transient、volatile,但方法表没有,并且字段可能是枚举类型,但方法肯定不会是枚举类型

  • 方法表上添加了synchroniced、native、strictfp和abstract关键字可用

因此会出现一些出入

| 标志名称 | 标志值 | 含义 |

| — | — | — |

| ACC_PUBLIC | 0x0001 | 是否为public |

| ACC_PRIVATE | 0x0002 | 是否为private |

| ACC_PROTECTED | 0x0004 | 是否为protected |

| ACC_STATIC | 0x0008 | 是否为static |

| ACC_FINAL | 0x0010 | 是否为final |

| ACC_Synchronized | 0x0040 | 是否为synchronized |

| ACC_SYTHETIC | 0x1000 | 是否由编译器自动产生(一般都是代码产生) |

| ACC_VARAGES | 0x0080 | 方法是否接受不定参数 |

| ACC_BRIDGE | 0x4000 | 方法是不是由编译器产生的桥接方法 |

| ACC_STRICT | 0x0800 | 方法是否为strictfp |

| ACC_ABSTRACT | 0x0400 | 方法是不是abstract |

| ACC_Native | 0x0100 | 方法是否为native |

名称与类型常量池引用

与字段表一样,都是使用name_index,descriptor_index来指向常量池中的常量,上面已经详细说明了形式是如何的

【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】

浏览器打开:qq.cn.hn/FTf 免费领取
Code

至此,方法的名称、返回值、修饰符都已经有地方存储了,那对于方法里面的代码呢?

方法里的Java代码,经过Javac编译器编译成字节码指令之后,会存放在方法属性表集合中的一个名为code的属性里面,所以方法表的属性表其实跟字段表一样,也是去存放扩展的一些额外信息的

而且,与字段表一样,来自父类的方法如果没有进行重写也是不会出现在方法表中的,或者接口中的默认方法没有进行重写也是不会出现在方法表中的

属性表集合

在字段表集合、方法表集合甚至Class文件中都有属性表这个概念,下面就来对这个属性表来分析

Class文件对其他的数据项目要求严格的顺序、长度和内容不同,而属性表没有Class文件那么严格,不要求各个属性表具有严格的顺序,甚至允许只要不与已有的属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,属性表中的属性现在已经有29项了,下面只挑选一些关键、重要的属性讲解

首先我们要认识属性表的结构,属性表中存放着各个属性,而各个属性的结构如下

| 类型 | 名称 | 数量 |

| — | — | — |

| u2 | attribute_name_index:代表属性的名称,是一个指向常量表CONSTANT_Utf8_info结构的索引 | 1 |

| u4 | attribute_length:代表属性的长度,以字节为单位 | 1 |

| u1 | info:代表属性内容,u1类型代表了内容肯定会是1个字节的倍数 | attribute_length |

Code属性

在方法表中提到过,方法表中存储方法的代码的地方是在方法的集合表里面(注意是方法的集合表,每个方法都有自己的集合表),具体来说是集合表中的Code属性,当然如果没有方法代码的话也就没有Code属性表,一个方法对应就是一个Code属性表

下面就来看看Code属性表的结构

| 类型 | 名称 | 数量 |

| — | — | — |

| u2 | attribute_name_index | 1 |

| u4 | attribute_length | 1 |

| u2 | max_stack | 1 |

| u2 | max_locals | 1 |

| u4 | code_length | 1 |

| u1 | code | code_length |

| u2 | exception_table_length | 1 |

| exception_info | exception_table | exception_table_length |

| u2 | attributes_count | 1 |

| attribute_info | attributes | attributes_count |

下面来说明一下这几个属性的作用

  • attribute_name_index:代表了属性的名称,指向了常量池的一个CONSTANT_Utf8_info结构的索引,里CONSTANT_Utf8_info里面存储的是固定的Code字符串

  • attribute_length:代表了属性值的长度,说白了就是集合表接下来的属性的长度,可以看到属性的名称与属性值的长度所占用的空间是6个字节(u2+u4),所以属性值的长度等于整个属性表减去6个字节(attribute_length里面就是这个值)

  • max_stack:max_stack代表了操作数栈深度的最大值,每个方法执行时,任意时刻都不会超过这个深度,虚拟机运行的时候会根据这个值去分配栈帧中的操作栈深度

  • max_locals:代表了局部变量表所需的存储空间,方法里面会有变量,这些变量就存放在临时变量表上,max_locals的单位是变量槽,变量槽是虚拟机为局部变量分配内存所使用的最小单位,对于长度不超过32位的数据类型,每个局部变量只会占用一个变量槽,这里要注意的一个地方,变量槽的数量不是方法里面的局部变量的数量来唯一确定的,操作数栈和局部变量表直接决定一个该方法的栈帧所耗费的内存,不必要的操作数栈深度和变量槽数量会造成内存的浪费,而避免浪费的主要做法就是及时回收不再使用的变量槽,当局部变量的作用域范围过了之后,该局部变量所占用的变量槽就要马上进行回收,让其他局部变量可以马上使用,所以,Java编译器会根据变量的作用域来分配变量槽给各个变量进行使用,并且根据同时生存的最大局部变量数量和类型计算出Max_locals的大小

  • code_length:代表了方法经过Java源程序编译后生成的字节码指令的长度,也是以字节为单位,u4为4个字节,理论上存储的最大值为2^32,但JVM虚拟机明确限制了一个方法不允许超过哦65535条字节质量,也就是2 ^ 16,如果超过了这个数量,Javac编译器就会拒绝编译

  • code:代表了方法经过Java源程序编译后生成的字节码指令,code存储着用于存储字节码指令的一系列字节流,字节码指令,顾名思义,就是一个指令就是一个字节码,长度就是一个字节,当JVM读到code里面的一个字节码时就可以找到字节码对应的字节码指令

Code属性可以说是Class文件中最重要的一个属性,如果将Java程序中的信息分为代码和元数据

  • 代码:方法体里面的Java代码

  • 元数据:类信息、字段信息、方法定义以及其他信息

那么Code属性就代表着代码的一部分,而Class文件另外的其他数据项来形容元数据

接下来我们使用一个简单的Java代码来看看

package offer.testSome;

/**

  • @Author: Ember

  • @Date: 2021/10/26 19:29

  • @Description:

*/

public class TestClass {

private int m;

public int inc(){

return m + 1;

}

}

使用Java -v 得到的class文件信息

在这里插入图片描述

可以看到有我们之前提到的版本、还有常量池信息

在这里插入图片描述

接下来就是code信息

这里的code在init方法里面,就是缺省的一个构造函数,但这里有一个其他的点,可以看到,args_size参数竟然为1,代表有一个参数

接下来看看自己写的inc方法

在这里插入图片描述

可以看到,里面的args_size也为1

这是为什么呢?明明两个方法都没有参数,为什么args_size为1,并且locals也给这个变量给了变量槽

这是因为this指针,在类中,我们可以在成员方法使用this指针,但我们没有给this指针一个参数的位置,所以,从这里我们可以看出this指针的实现,通过在Javac编译器编译的时候把this关键字的访问转变为对一个普通方法参数的访问,然后在虚拟机调用实例方法时候自动传入此参数而已,因此在方法里面的code属性中,至少会有一个参数,局部变量表也会预留一个变量槽位来存放对象实例的引用,但这个情况只对成员方法有效,如果改成静态方法,是不会有的

在这里插入图片描述

可以看到,在改为static之后,args_size就为0了,这是因为static修饰后就没有this这个指针变量了

接下来的就是该方法的显示异常处理表集合了,用来存储该方法try捕捉的异常,异常表对于Code属性来说并不是必须存在的,比如上面的栗子就没有异常表生成

异常表

异常表的格式如下所示,总共包含4个字段

| 类型 | 名称 | 数量 |

| — | — | — |

| u2 | start_pc | 1 |

| u2 | end_pc | 1 |

| u2 | handler_pc | 1 |

| u2 | catch_type | 1 |

下面就说明一下这4个字段的含义

  • start_pc:try语句位于的行数

  • end_pc:try语句结束的行数,start_pc和end_pc形成了一个行数范围,刚好就是try语句块,只要在这范围之间出现了类型为catch_type的异常或者其子类的异常就会转到handler_pc行继续处理,注意这个行数范围不是指代码,而是指编译成的字节码指令的范围

  • handler_pc:这个是负责记录处理异常是交由第几行实现的

  • catch_type:标识捕捉的异常类型,是一个指向常量池中CONSTANT_Class_info型常量的索引,当catch_type为0时,代表任意的异常情况都会转到handler_pc去进行处理

下面就来看看这个异常表是怎样的

在这里插入图片描述

可以看到,from其实就代表start_pc、to就代表end_pc,target代表的就是handler_pc,type其实对应的是常量池的CONSTANT_Class_info,为一个 符号引用

可以看到从第0条命令到第5条命令,只要在这里出现已经Exception异常,就会交由第6条命令去执行,0~5的命令就是执行逻辑并且返回值而已

加上finally语句之后,就会变成如下这样

在这里插入图片描述

在这里插入图片描述

可以看到,多出了三个any,any代表的就是任何的异常,可以看到一个有趣的现象,我们可以0~6的命令出现了Exception异常会跳到第8条命令去处理,这里与上面不加finally最大的不同就是没有经过return的命令,接下来就是0 ~ 6的命令出现了any异常会跳到了第十五行,然后8~13行的anu异常也会跳到第15行,15 ~ 16行的any异常也会跳到第15行

也就是有三条

  • 0~6的Exception交由8处理,即try语句块

  • 0~6出现不属于Exception的异常,会交由15处理,即try语句块

  • 8~13出现异常,会交由15处理,即catch语句块

  • 15~16的异常也会交由15处理,也就是finally语句块

可能大家这里就会觉得,不是说finally语句块的return会将返回值给截断的吗?但看异常表的情况好像要出现异常才可以走到finally语句块呀,但其实finally的关键字的作用没有那么简单,下面我们仔细分析一下字节码指令

在这里插入图片描述

整个指令执行的过程

  • 获取静态值

  • 取出静态值赋予i

  • i进行自增

  • 对i进行存储

  • 在这里,本来就应该返回值了,但finally语句块导致了这里将i又变为1

  • 然后返回i

  • 接下来给是astore指令,将catch中定义的Exception赋值,并且放到变量槽2中

  • 。。。

可以看到,其实finally会将正常返回值的字节码指令都进行替换,替换成finally里面的返回指令,所以压根不会正常返回,一定会经过finally的return命令

LineNumberTable属性

LineNumberTable属性是用来描述Java源码行号与字节码指令行号之间的对应关系,它并不是运行时必须道德属性,但默认会生成到CLASS文件之中的,我们也可以同属-g:none来取消生成这项信息

如果不选择这个信息,最主要的影响就是当抛出异常的时候,堆栈中是不会显示出错的代码行号,并且在调试程序的时候,也无法按照源码来设置断点

在这里插入图片描述

可以看到,LineNumberTable将代码行号与字节码命令关联在了一起

LineNumberTable的结构如下所示

| 类型 | 名称 | 数量 |

| — | — | — |

| u2 | attribute_name_index | 1 |

| u4 | attribute_length | 1 |

| u2 | line_number_table_length | 1 |

| line_number_info | line_number_table | line_number_table_length |

  • attribute_name_index:指向一个CONSTANT_Utf8的结构,里面固定的字符串就是LineNumberTable

  • attribute_length:存储的就是字符串的长度,而且是以字节为单位

  • line_number_table_length:存储的是下面line_number_info的长度,而且也是以字节为单位

  • line_number_info:里面包含了两个数据项

  • start_pc:u2类型,里面存储的就是字节码指令的行号

  • line_number:u2类型,里面存储的是源代码的行号,也就是Java源代码的行号

  • 这两个数据项就是上图中的数据项

因为下面的属性都会出现attribute_name_index和attribute_length这两个属性,所以就不会再进行赘述了

LocalVariableTable和LocalVariableTypeTable属性

LocalVariableTable是用来描述栈帧中局部变量表的变量与Java源码中定义的变量名之间的关系,它也不是一个必要的属性,也是可以进行取消生成的,取消的后果是参数名称丢失,看到的只有arg0和arg1这些东西,也就是说占位符取代了原先的参数名,并且对于调试期间无法根据参数名称从上下文获得参数值

在这里插入图片描述

LocalVariableTable的架构如下

| 类型 | 名称 | 数量 |

| — | — | — |

| u2 | attribute_name_index | 1 |

| u2 | attribute_length | 1 |

| u2 | local_variable_table_length | 1 |

| local_variable_info | local_variable_table | local_variable_table_length |

  • 前两个属性不再赘述

  • local_variable_table_length:代表local_variable_table的长度,单位为字节

  • local_variable_info:存储的就是方法的参数与栈帧的关联

  • start_pc:从第几行的字节码指令行出现,也就是该局部变量的生命周期开始的字节码偏移量,对应start

  • length:代表该局部变量的生命周期的作用范围覆盖的长度,对应length

  • name_index:指向常量池中的CONSTANT_Utf8_info结构的索引,代表了参数的名字,对应name

  • descriptor_index:指向常量池中的CONSTANT_Utf8_info结构的索引,代表了该参数的描述符(前面已经说明过了名字与描述符的区别),对应Signature

  • index:代表了该变量在栈帧的局部变量表中的变量槽的位置,如果该变量的数据类型为64位时,则会占据两个变量槽,即index和Index+1两个变量槽,对应slot

拓展:关于descriptor_index,在JDK5引入泛型之后,descriptor_index其实就被改成了Signature了,也就是特征签名,对于非泛型的数据来说,描述符和特征签名描述的信息是一致的,但如果是泛型,描述符会把泛型的参数化类型给擦除掉,所以不能表示泛型,所以改用了Signature

Signature属性

Signature属性出现在属性表中,是用来支持泛型签名的、记录泛型类型的,后面再仔细研究这个伪泛型、泛型擦除等问题

Signature属性结构如下

| 类型 | 名称 | 数量 |

| — | — | — |

| u2 | attribute_name_index | 1 |

| u4 | attribute_length | 1 |

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值