虚拟机执行子系统

一、类文件结构

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=1;
return x;
}catch(Exception e){
x=2;
return 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属性

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

    

      line_number_table:数量为line_number_table_length、类型为line_number_info的集合,

      line_number_info:表包括了start_pc和line_number两个u2类型的数据项,前者是字节码行号,后者是Java源码行号

  4.LocalVariableTable属性

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

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

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

    

    local_variable_info:代表了一个栈帧与源码中的局部变量的关联

    

    

    start_pc和length:分别代表了这个局部变量的生命周期开始的字节码偏移量及其作用范围覆盖的长度,两者结合起来就是这个局部变量在字节码之中的作用域范围。

    name_index和descriptor_index:指向常量池中CONSTANT_Utf8_info型常量的索引,分别代表了局部变量的名称以及这个局部变量的描述符。

    index:这个局部变量在栈帧局部变量表中Slot的位置。当这个变量数据类型是64位类型时(double和long),它占用的Slot为index和index+1两个

    泛型的支持属性

      在JDK 1.5引入泛型之后,LocalVariableTable属性增加了一个“姐妹属性”:LocalVariableTypeTable

      这个新增的属性结构与LocalVariableTable非常相似,仅仅是把记录的字段描述符的descriptor_index替换成了字段的特征签名(Signature),

      对于非泛型类型来说,描述符和特征签名能描述的信息是基本一致的,但是泛型引入之后,由于描述符中泛型的参数化类型被擦除掉

    描述符就不能准确地描述泛型类型了,因此出现了LocalVariableTypeTable。

  5.SourceFile属性

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

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

    

    sourcefile_index:指向常量池中CONSTANT_Utf8_info型常量的索引,常量值是源码文件的文件名

  6.ConstantValue属性

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

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

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

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

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

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

    

    ConstantValue属性是一个定长属性

    attribute_length:数据项值必须固定为2。

    constantvalue_index:数据项代表了常量池中一个字面量常量的引用

      根据字段类型的不同,字面量可以是CONSTANT_Long_info、CONSTANT_Float_info、CONSTANT_Double_info、CONSTANT_Integer_info、CONSTANT_String_info常量中的一种

   7.InnerClasses属性

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

    

    number_of_classes:代表需要记录多少个内部类信息,每一个内部类的信息都由一个inner_classes_info表进行描述

    

    inner_class_info_index和outer_class_info_index:指向常量池中CONSTANT_Class_info型常量的索引,分别代表了内部类和宿主类的符号引用。

    inner_name_index:指向常量池中CONSTANT_Utf8_info型常量的索引,代表这个内部类的名称,如果是匿名内部类,那么这项值为0。

    inner_class_access_flags:内部类的访问标志,类似于类的access_flags

    

   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)类文件结构的更多相关文章

  1. 深入理解Java虚拟机(类文件结构)

    深入理解Java虚拟机(类文件结构) 欢迎关注微信公众号:BaronTalk,获取更多精彩好文! 之前在阅读 ASM 文档时,对于已编译类的结构.方法描述符.访问标志.ACC_PUBLIC.ACC_P ...

  2. Java虚拟机,类文件结构深度解析

    Java类文件结构 Java虚拟机不和包括Java在内的任何语言绑定,只与 "Class文件" 这种特定的二进制文件所关联, Class文件中包含了Java虚拟机指令集合符号表以及 ...

  3. 《深入理解Java虚拟机》类文件结构

    上节学习回顾 在上一节当中,主要以自己的工作环境简单地介绍了一下自身的一些调优或者说是故障处理经验.所谓百变不离其宗,这个宗就是我们解决问题的思路了. 本节学习重点 在前面几章,我们宏观地了解了虚拟机 ...

  4. 深入理解Java虚拟机(类文件结构+类加载机制+字节码执行引擎)

    目录 1.类文件结构 1.1 Class类文件结构 1.2 魔数与Class文件的版本 1.3 常量池 1.4 访问标志 1.5 类索引.父索引与接口索引集合 1.6 字段表集合 1.7 方法集合 1 ...

  5. Java虚拟机 - Class类文件结构

    [深入Java虚拟机]之二:Class类文件结构 平台无关性 Java是与平台无关的语言,这得益于Java源代码编译后生成的存储字节码的文件,即Class文件,以及Java虚拟机的实现.不仅使用Jav ...

  6. Java虚拟机——Class类文件结构

    Class文件格式采用一种类似C语言结构体的结构来存储数据,这种数据结构只有两种数据类型:无符号数和表.      无符号数属于基本的数据类型,数据项的不同长度分别用u1, u2, u4, u8表示, ...

  7. 深入理解Java虚拟机(七)——类文件结构

    Java的无关性 由于计算机领域中有很多操作系统和硬件平台同时在竞争,所以,很多编程语言的程序设计会与其运行的平台和操作系统产生耦合,这样就大大增加了程序员的工作,为了适应不同的平台,需要修改很多代码 ...

  8. 深入java虚拟机学习 -- 类的加载机制

    当看到"类的加载机制",肯定很多人都在想我平时也不接触啊,工作中无非就是写代码,不会了可以百度,至于类,jvm是怎么加载的我一点也不需要关心.在我刚开始工作的时候也觉得这些底层的内 ...

  9. 深入java虚拟机学习 -- 类的加载机制(续)

    昨晚写 深入java虚拟机学习 -- 类的加载机制 都到1点半了,由于第二天还要工作,没有将上篇文章中的demo讲解写出来,今天抽时间补上昨晚的例子讲解. 这里我先把昨天的两份代码贴过来,重新看下: ...

  10. 深入java虚拟机学习 -- 类的卸载

    类的生命周期 在开始本节之前让我们再来回顾下类的生命周期 没看过前6个过程的同学建议从头看下<深入java虚拟机学习 -- 类的加载机制>,这里就不再过多介绍了,着重说下类的卸载 类的卸载 ...

随机推荐

  1. java多线程 —— 两种实际应用场景模拟

    最近做的偏向并发了,因为以后消息会众多,所以,jms等多个线程操作数据的时候,对共享变量,这些要很注意,以防止发生线程不安全的情况. (一) 先说说第一个,模拟对信息的发送和接收.场景是这样的: 就像 ...

  2. android应用搬家的实现

    android手机上的应用搬家功能,具体的介绍和原理参考: 系统目录及应用搬家的研究 应用搬家的实现 这里主要是作为一个补充,因为上面两篇文章虽然讲的挺好的,但是给出的例子不能直接运行,还是需要一些准 ...

  3. 使用SignalR 2进行服务器广播

    概述 在本教程中,您将创建一个股票代码应用程序,该应用程序代表您希望定期“推送”或广播从服务器到所有连接客户端的通知的实时应用程序.在本教程的第一部分中,您将从头开始创建该应用程序的简化版本.在本教程 ...

  4. Net Core SignalR 测试,可以用于unity、Layair、白鹭引擎、大数据分析平台等高可用消息实时通信器。

    SignalR介绍 SignalR介绍来源于微软文档,不过多解释.https://docs.microsoft.com/zh-cn/aspnet/core/signalr/introduction?v ...

  5. 超简单工具puer——“低碳”的前后端分离开发

    本文由作者郑海波授权网易云社区发布. 前几天,跟一同事(MIHTool作者)讨教了一下开发调试工具.其实个人觉得相较于定制一个类似MIHTool的Hybrid App容器,基于长连的B/S架构的工具其 ...

  6. dos新建文件夹 新建文件

    https://jingyan.baidu.com/article/49ad8bceb0237f5834d8fa19.html 新建文件夹: mkdir kkk 新建kkk文件夹 新建文件: type ...

  7. BitMap算法详解

    所谓的BitMap就是用一个bit位来标记某个元素所对应的value,而key即是该元素,由于BitMap使用了bit位来存储数据,因此可以大大节省存储空间. 基本思想: 这此我用一个简单的例子来详细 ...

  8. linux中进程和计划任务管理

    进程和计划任务管理 1. 程序和进程的关系 程序:保存在硬盘.光盘等介质中的可执行代码和数据:静态保存的代码 进程:在 CPU 及内存中运行的程序代码:动态执行的代码:父.子进程:每个进程可以创建一个 ...

  9. #阿里云#云服务器部署可道云(KodExplorer)

    前言:在做一些项目的时候,经常有一些文档交流,修改之后的文档在QQ或微信上发来发去,还要下载,很是不爽,有一个挺有用的东西叫做KodExplorer可道云. kodexplorer可道云是目前国内有代 ...

  10. constructor 属性返回变量或对象的构造函数。判断是否为日期,数组的例子

    constructor 属性返回变量或对象的构造函数. <!DOCTYPE html> <html> <head> <meta charset="u ...