首页 > 代码库 > 深入了解Java虚拟机(3)类文件结构

深入了解Java虚拟机(3)类文件结构

 虚拟机执行子系统

一、类文件结构

 技术分享

1.魔数和class版本

  1.magic-魔数:0xCAFEBABE;4字节

  2.minor_version:次版本,丶之后的数字;2字节

  3.major_version:主版本,丶之前的数字;2字节

2.常量池

  1.constant_pool_count:常量池常量数量(= 此值 - 1):2字节

    由于常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项u2类型的数据,代表常量池容量计数值。

  2.constant_pool:常量,第一位为类型位,之后的就是按照各自常量的定义:n字节

    技术分享

   技术分享

   技术分享

   技术分享

3.访问标识符

   1.access_flags:访问标识

     这个标志用于识别一些类或者接口层次的访问信息,包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型;如果是类的话,是否被声明为final等。

   技术分享

    如:0x0001 0x0020说明是一个公共的类

4.类索引、父类索引、接口索引:Class文件中由这三项数据来确定这个类的继承关系

  1.this_class:类索引:2字节

    类索引用于确定这个类的全限定名

  2.super_class:父类索引:2字节

    父类索引用于确定这个类的父类的全限定名。由于Java语言不允许多重继承,所以父类索引只有一个,除了java.lang.Object之外,所有的Java类都有父类,因此除了java.lang.Object外,所有Java类的父类索引都不为0

  3.interfaces:接口索引:2字节数组   

    接口索引集合就用来描述这个类实现了哪些接口,这些被实现的接口将按implements语句(如果这个类本身是一个接口,则应当是extends语句)后的接口顺序从左到右排列在接口索引集合中

    接口索引开头为数量:2字节

  查找

    类索引、父类索引和接口索引集合都按顺序排列在访问标志之后,类索引和父类索引用两个u2类型的索引值表示,它们各自指向一个类型为CONSTANT_Class_info的类描述符常量,通过CONSTANT_Class_info类型的常量中的索引值可以找到定义在CONSTANT_Utf8_info类型的常量中的全限定名字符串

    技术分享

5.字段表集合 

可以包括的信息有:

  字段的作用域(public、private、protected修饰 符)、是实例变量还是类变量(static修饰符)、可变性(final)、并发可见性(volatile修饰 符,是否强制从主内存读写)、可否被序列化(transient修饰符)、字段数据类型(基本类型、对象、数组)、字段名称。上述这些信息中,各个修饰符都是布尔值,要么有某个修饰符,要么没有,很适合使用标志位来表示。

  字段名、字段数据类型,这些都是无法固定的,只能引用常量池中的常量来描述

   技术分享

  1.access_flags:字段访问标识:u2

    技术分享

  2.name_index:字段简单名称:u2

    引用常量池常量

  3.descriptor_index:方法描叙符:u2

    描叙字段:字段类型

    描叙方法:(参数列表)描叙符

    引用常量池常量

     技术分享

 

 6.方法表集合

  技术分享

  技术分享

 

7.class、字段、方法等的属性表

  预定义的有21种,每种都有自己的结构

  1.Code属性

    技术分享

 

    attribute_name_index是一项指向CONSTANT_Utf8_info型常量的索引,常量值固定为“Code”,它代表了该属性的属性名称

    attribute_length指示了属性值的长,所以属性值的长度固定为整个属性表长度减去6个字节。

    max_stack代表了操作数栈(Operand Stacks)深度的最大值。在方法执行的任意时刻,操作数栈都不会超过这个深度。虚拟机运行的时候需要根据这个值来分配栈帧(StackFrame)中的操作栈深度。

    max_locals代表了局部变量表所需的存储空间。

    code_length和code用来存储Java源程序编译后生成的字节码指令。

    code:字节码

    exception:异常表

      技术分享

 

      如果当字节码在第start_pc行[1]到第end_pc行之间(不含第end_pc行)出现了类型为catch_type或者其子类的异常(catch_type为指向一个CONSTANT_Class_info型常量的索引)

    则转到第handler_pc行继续处理。当catch_type的值为0时,代表任意异常情况都需要转向到handler_pc处进行处理

//Java源码
public int inc(){
    int x;
    try{
        x=1return x;
    }catch(Exception e){
        x=2return x;
    }finally{
        x=3;
    }
}/

/编译后的ByteCode字节码及异常表
public int inc();
Code:
Stack=1,Locals=5,Args_size=1
0:iconst_1//try块中的x=1
1:istore_1
2:iload_1//保存x到returnValue中,此时x=1
3:istore 4
5:iconst_3//finaly块中的x=3
6:istore_1
7:iload 4//将returnValue中的值放到栈顶,准备给ireturn返回
9:ireturn
10:astore_2//给catch中定义的Exception e赋值,存储在Slot 2中
11:iconst_2//catch块中的x=2
12:istore_1
13:iload_1//保存x到returnValue中,此时x=2
14:istore 4
16:iconst_3//finaly块中的x=3
17:istore_1
18:iload 4//将returnValue中的值放到栈顶,准备给ireturn返回
20:ireturn
21:astore_3//如果出现了不属于java.lang.Exception及其子类的异常才会走到这里
22:iconst_3//finaly块中的x=3
23:istore_1
24:aload_3//将异常放置到栈顶,并抛出
25:athrow
Exception table:
from to target type
0 5 10 Class java/lang/Exception
0 5 21 any
10 16 21 any            

 

      异常表实际上是Java代码的一部分,编译器使用异常表而不是简单的跳转命令来实现Java异常及finally处理机制

  2.Exception属性

    方法的异常

    技术分享

    number_of_exceptions项表示方法可能抛出number_of_exceptions种受查异常

    exception_index_table是一个指向常量池中CONSTANT_Class_info型常量的索引,代表了该受查异常的类型

  3.LineNumberTable属性

    LineNumberTable属性用于描述Java源码行号与字节码行号(字节码的偏移量)之间的对应关系它并不是运行时必需的属性,但默认会生成到Class文件之中

    可以在Javac中分别使用-g:none或-g:lines选项来取消或要求生成这项信息。如果选择不生成LineNumberTable属性

    对程序运行产生的最主要的影响就是当抛出异常时,堆栈中将不会显示出错的行号,并且在调试程序的时候,也无法按照源码行来设置断点

  4.LocalVariableTable属性

    LocalVariableTable属性用于描述栈帧中局部变量表中的变量与Java源码中定义的变量之间的关系,它也不是运行时必需的属性

    但默认会生成到Class文件之中,可以在Javac中分别使用-g:none或-g:vars选项来取消或要求生成这项信息。

    如果没有生成这项属性,最大的影响就是当其他人引用这个方法时,所有的参数名称都将会丢失,IDE将会使用诸如arg0、arg1之类的占位符代替原有的参数名,这对程序运行没有影响,但是会对代码编写带来较大不便,而且在调试期间无法根据参数名称从上下文中获得参数值

  5.SourceFile属性

    SourceFile属性用于记录生成这个Class文件的源码文件名称。这个属性也是可选的,可以分别使用Javac的-g:none或-g:source选项来关闭或要求生成这项信息。

    在Java中,对于大多数的类来说,类名和文件名是一致的,但是有一些特殊情况(如内部类)例外。如果不生成这项属性,当抛出异常时,堆栈中将不会显示出错代码所属的文件名

  6.ConstantValue属性

    ConstantValue属性的作用是通知虚拟机自动为静态变量赋值。

    只有被static关键字修饰的变量(类变量)才可以使用这项属性。

    对于非static类型的变量(也就是实例变量)的赋值是在实例构造器<init>方法中进行的;

    而对于类变量,则有两种方式可以选择:

      如果同时使用final和static来修饰一个变量,并且这个变量的数据类型是基本类型或者java.lang.String的话,就生成ConstantValue属性来进行初始化

      如果这个变量没有被final修饰,或者并非基本类型及字符串,则将会选择在<clinit>方法中进行初始化

   7.InnerClasses属性

    InnerClasses属性用于记录内部类与宿主类之间的关联。如果一个类中定义了内部类,那编译器将会为它以及它所包含的内部类生成InnerClasses属性

   8.Deprecated及Synthetic属性

    Deprecated属性用于表示某个类、字段或者方法,已经被程序作者定为不再推荐使用,它可以通过在代码中使用@deprecated注释进行设置。

    Synthetic属性代表此字段或者方法并不是由Java源码直接产生的,而是由编译器自行添加的,

   9.StackMapTable属性

    字节码验证

  10.Signature属性

     泛型被擦出后,获取泛型信息

  11.BootstrapMethods属性

    BootstrapMethods属性在JDK 1.7发布后增加到了Class文件规范之中,它是一个复杂的变长属性,位于类文件的属性表中。这个属性用于保存invokedynamic指令引用的引导方法限定符。

 

 8.字节码指令简介

  1.加载和存储指令

    加载和存储指令用于将数据在栈帧中的局部变量表和操作数栈(见第2章关于内存区域的介绍)之间来回传输,这类指令包括如下内容。  

    将一个局部变量加载到操作栈:iload、iload_<n>、lload、lload_<n>、fload、fload_<n>、dload、dload_<n>、aload、aload_<n>。

    将一个数值从操作数栈存储到局部变量表:istore、istore_<n>、lstore、lstore_<n>、fstore、fstore_<n>、dstore、dstore_<n>、astore、astore_<n>。

    将一个常量加载到操作数栈:bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_m1、iconst_<i>、lconst_<l>、fconst_<f>、dconst_<d>。

    扩充局部变量表的访问索引的指令:wide。

  2.运算指令

    加法指令:iadd、ladd、fadd、dadd。
    减法指令:isub、lsub、fsub、dsub。
    乘法指令:imul、lmul、fmul、dmul。
    除法指令:idiv、ldiv、fdiv、ddiv。
    求余指令:irem、lrem、frem、drem。
    取反指令:ineg、lneg、fneg、dneg。
    位移指令:ishl、ishr、iushr、lshl、lshr、lushr。
    按位或指令:ior、lor。
    按位与指令:iand、land。
    按位异或指令:ixor、lxor。
    局部变量自增指令:iinc。
    比较指令:dcmpg、dcmpl、fcmpg、fcmpl、lcmp。

   3.类型转换指令

    Java虚拟机直接支持(即转换时无需显式的转换指令)以下数值类型的宽化类型转换(Widening Numeric Conversions,即小范围类型向大范围类型的安全转换):

      int类型到long、float或者double类型。

      long类型到float、double类型。

      float类型到double类型。
    处理窄化类型转换(Narrowing Numeric Conversions)

      这些转换指令包括:i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l和d2f。

   4.对象访问与创建

    创建类实例的指令:new。

    创建数组的指令:newarray、anewarray、multianewarray。

    访问类字段(static字段,或者称为类变量)和实例字段(非static字段,或者称为实例变量)的指令:getfield、putfield、getstatic、putstatic。

    把一个数组元素加载到操作数栈的指令:baload、caload、saload、iaload、laload、faload、daload、aaload。

    将一个操作数栈的值存储到数组元素中的指令:bastore、castore、sastore、iastore、fastore、dastore、aastore。

    取数组长度的指令:arraylength。

    检查类实例类型的指令:instanceof、checkcast。

  5.操作数栈管理指令

    如同操作一个普通数据结构中的堆栈那样,Java虚拟机提供了一些用于直接操作操作数栈的指令,

    包括:

      将操作数栈的栈顶一个或两个元素出栈:pop、pop2。

      复制栈顶一个或两个数值并将复制值或双份的复制值重新压入栈顶:dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2。

      将栈最顶端的两个数值互换:swap

  6.控制转移指令

    条件分支:ifeq、iflt、ifle、ifne、ifgt、ifge、ifnull、ifnonnull、if_icmpeq、if_icmpne、if_icmplt、if_icmpgt、if_icmple、if_icmpge、if_acmpeq和if_acmpne。

    复合条件分支:tableswitch、lookupswitch。

    无条件分支:goto、goto_w、jsr、jsr_w、ret。

  7.方法调用和返回

    invokevirtual指令用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),这也是Java语言中最常见的方法分派方式。

    invokeinterface指令用于调用接口方法,它会在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用。

    invokespecial指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法。

    invokestatic指令用于调用类方法(static方法)。

    invokedynamic指令用于在运行时动态解析出调用点限定符所引用的方法,并执行该方法,前面4条调用指令的分派逻辑都固化在Java虚拟机内部,

    invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的。方法调用指令与数据类型无关,而方法返回指令是根据返回值的类型区分的,包括ireturn(当返回值是boolean、byte、char、short和int类型时使用)、lreturn、freturn、dreturn和areturn,另外还有一条return指令供声明为void的方法、实例初始化方法以及类和接口的类初始化方法使用

  8.异常处理指令

    在Java程序中显式抛出异常的操作(throw语句)都由athrow指令来实现

    除了用throw语句显式抛出异常情况之外,Java虚拟机规范还规定了许多运行时异常会在其他Java虚拟机指令检测到异常状况时自动抛出。

    例如,在前面介绍的整数运算中,当除数为零时,虚拟机会在idiv或ldiv指令中抛出ArithmeticException异常。

    而在Java虚拟机中,处理异常(catch语句)不是由字节码指令来实现的(很久之前曾经使用jsr和ret指令来实现,现在已经不用了),而是采用异常表来完成的。

  9.同步指令

    同步一段指令集序列通常是由Java语言中的synchronized语句块来表示的

    Java虚拟机的指令集中有monitorenter和monitorexit两条指令来支持synchronized关键字的语义

void onlyMe(Foo f){
    synchronized(f){
    doSomething();
    }
}

// 指令码
Method void onlyMe(Foo)
0 aload_1//将对象f入栈
1 dup//复制栈顶元素(即f的引用)
2 astore_2//将栈顶元素存储到局部变量表Slot 2中
3 monitorenter//以栈顶元素(即f)作为锁,开始同步
4 aload_0//将局部变量Slot 0(即this指针)的元素入栈
5 invokevirtual#5//调用doSomething()方法
8 aload_2//将局部变量Slow 2的元素(即f)入栈
9 monitorexit//退出同步
10 goto 18//方法正常结束,跳转到18返回
13 astore_3//从这步开始是异常路径,见下面异常表的Taget 13
14 aload_2//将局部变量Slow 2的元素(即f)入栈
15 monitorexit//退出同步
16 aload_3//将局部变量Slow 3的元素(即异常对象)入栈
17 athrow//把异常对象重新抛出给onlyMe()方法的调用者
18 return//方法正常返回
Exception table:
FromTo Target Type
4 10 13 any
13 16 13 any        

 

 

 

 

 

 

    

 

深入了解Java虚拟机(3)类文件结构