深入理解JVM虚拟机(四):Class类文件结构(二)

属性表在前面的讲解中出现多次,在Class文件、字段表、方法表都可以携带自己的属性表集合,用于描叙某些场景专有的信息。为了正确解析Class文件,《Java虚拟机规范(第二版)》中预定义了9项虚拟机实现应当识别的属性。然而在最新的《Java虚拟机规范(Java SE7)》中属性表已经增加到了21项。当然我们不用全部记住它们,只需要熟悉其中的几个关键属性。

1.属性表的基本结构

本次博客只谈其中的5种属性
在这里插入图片描述

每个属性的名称都引用自常量池中一个CONSTANT_Utf8_info类型的常量来表示,属性值的结构则完全是自定义,只需要一个u4长度属性去说明属性值所占用的位数即可。

基本属性表的结构图如下所示:

在这里插入图片描述

1.1 Code属性

Java方法体中的代码经过Javac编译之后,最终变为字节码存储在Code属性中,Code属性出现在方法表的属性集合之中。接口或抽象类中的方法并不存在Code属性,因为他们并没有对方法进行具体的实现

Code属性表的结构如下图:
在这里插入图片描述

在这里我尝试挑几个概念上比较重要来进行记录。

  1. max-stack,要解释清楚它,需要了解操作数栈,栈帧等知识,所以这个属性字段先不谈。

  2. max_locals,这个挺有意思。它代表了局部变量所需的存储空间。单位是Slot,虚拟机为局部变量分配内存使用的最小单位。对于byte、char… …这种长度不超过4个字节的数据类型,每个局部变量占用一个Slot,而double、long这两种64位的数据类型则需要两个Slot来存放。方法参数(this)、显式异常处理器参数(try-catch)、方法体中的局部变量都需要使用局部变量表来存放。计算max_locals的值也不是方法中用到了多少个局部变量,就将每个局部变量所占用的Slot算出来最后进行简单求和。事实上局部变量表中的Slot可以重用,当代码执行超出一个局部变量的作用域时,这个局部变量所占用的Slot可以被其他局部变量所使用。Javac编译器会根据变量的作用域来分配Slot给每个变量使用,然后计算出max_locals的大小。

  3. code_length不用说代表的是字节码长度。它是一个u4类型的长度值,理论上最大值可以达到2的32次方-1,但Java虚拟机规范限制一个方法不允许超过65535条字节码指令,所以它实际只使用了u2的长度,一旦超过这个长度,编译器会拒绝编译。

Code属性是Class文件中最重要的一个属性,Java程序可以分为代码和元数据,也就是方法体中的代码与字段、类、方法定义等其他信息。因此在Class文件中,只有Code属性用于描述Java方法,其他都用于描述元数据。

我已将常量池部分省略:

{                                                                                        
  public com.basic.java.classStructure.TestClass();                                      
    descriptor: ()V                                                                      
    flags: ACC_PUBLIC                                                                    
    Code:                                                                                
      stack=1, locals=1, args_size=1                                                     
         0: aload_0                                                                      
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V    
         4: return                                                                       
      LineNumberTable:                                                                   
        line 7: 0                                                                        
      LocalVariableTable:                                                                
        Start  Length  Slot  Name   Signature                                            
            0       5     0  this   Lcom/basic/java/classStructure/TestClass;            
                                                                                         
  public int c();                                                                        
    descriptor: ()I                                                                      
    flags: ACC_PUBLIC                                                                    
    Code:                                                                                
      stack=2, locals=1, args_size=1                                                     
         0: aload_0                                                                      
         1: getfield      #2                  // Field m:I                               
         4: iconst_1                                                                     
         5: iadd                                                                         
         6: ireturn                                                                      
      LineNumberTable:                                                                   
        line 11: 0                                                                       
      LocalVariableTable:                                                                
        Start  Length  Slot  Name   Signature                                            
            0       7     0  this   Lcom/basic/java/classStructure/TestClass;            
}                                                                                        
SourceFile: "TestClass.java"                                                             

因为原Java文件中实际上有两个方法,一个实例构造器,一个inc方法。因此第一个public TestClass();代表的是实例构造器。我们可以看到args_size=1,说明这个方法有一个参数,虽然源代码里面两个方法都没有显式参数,但是我们都知道这个参数肯定是this。

当然,这个this只对实例对象有效,也就是说,如果inc方法被声明为static,那么它的args_size就会等于0。

往下看是三条字节码指令,关于字节码指令的内容目前也不进行讨论,所以暂且跳过。

异常表

按道理来说,字节码指令之下就是异常表(显式异常处理表),但是此代码并没有生成异常表。所以这个部分对Code来说并不是必须的。

来看一下异常表的结构:
在这里插入图片描述

异常表包含4个字段,这些字段的含义为:如果字节码从第start_pc到end_pc行之间(不包含第end_pc)行出现了类型为catch_type或其子类的异常(catch_type为指向一个CONSTANT_Class_info型常量的索引),则转到第handler_pc行继续处理。当catch_type的值为0时,代表任何的异常情况都需要转向到handler_pc行进行处理。

异常表实际上是Java代码的一部分,编译器使用异常表而不是简单的跳转命令来实现Java异常及finally处理机制。注:字节码的“行”是一种形象的描述,指的是字节码相对于方法体开始的偏移量,而不是Java源代码的行号。

1.2 Exception属性

属性表结构如下:

在这里插入图片描述

这里的Exceptions属性是在方法表中与Code属性平级的一项属性,而不是Code属性表中的异常属性表。Exceptions属性表的作用是列举出方法中可能抛出的受查异常(Checked Exception),也就是在方法描述时在throws关键字后面列举的异常。

此属性表中的number_of_exceptions项表示方法可能抛出number_of_exceptions种受检查异常,每一种受检查异常使用一个exception_index_table项表示,指向常量池中CONSTANT_Class_info型常量表的索引,代表了该受检查异常的类型。

猜你喜欢

转载自blog.csdn.net/qq_21125183/article/details/84958729