深入理解JVM-字节码

Class文件

       Java源程序被编译器编译后称为class文件,而Class文件则由自己的格式,其采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构只有两种数据类型,无符号数和表。正是因为class文件拥有自己的格式才使得Java程序可以跨平台执行。

Java字节码整体结构

字节数名称含义
4Magic Number魔数,值为0xCAFEBABE
2+2Version包括minor version和major version
2 + nConstant Pool字符串常量池
2Access Flags权限
2This Class Name当前类的全限定类名,指向常量池
2Super Class Name父类的全限定类名,指向常量池
2+nInterfaces所实现的接口以及接口表
2+nFields拥有的字段以及字段表
2+nMethods拥有的方法以及方法表
2+nAttributes拥有的属性以及属性表

在这里插入图片描述
在这里插入图片描述

Java字节码结构

       Class字节码中有两种数据类型:

  • 字节数据直接量:这是基本的数据类型。共分为u1、u2、u4、u8四种,分别代表连续的一个字节、2个字节、4个字节、8个字节组成的整体数据。
  • 表(数组):表是由多个基本数据或其他表,按照既定顺序组成的大的数据集合。表是有结构的,它的结构体现在:组成表的成分所在的位置和顺序都是已经严格定义好的。

常量池中数据类型的结构总表

在这里插入图片描述

       上面的表中描述了11中常用的数据类型,jdk1.7之后又增加了三种,CONSTANT_MethodHandle_info、CONSTANT_MethodType_info以及CONSTANT_MethodDynamic_info。

访问标志

在这里插入图片描述如果一个类是public以及final的,那么其标志值为分别对应的标志值相或,所以其标志值为0x0011。

字段表集合

字段表用于描述类和接口中声明的变量。这里的字段包含了类级别变量以及实例变量,但是不包括方法内部的局部变量。

fields_count: u2 (表示字段的个数)

在这里插入图片描述

方法表

method_count:u2 (表示方法的个数)
在这里插入图片描述

方法的属性结构

方法中的每个属性都是一个attribute_info结构

在这里插入图片描述

JVM规范预定义的attribute

  • JVM预定义了部分attribute,但是编译器自己也可以实现自己的attribute写入class文件里,供运行时使用。
  • 不同的attribute通过attribute_name_index来区分。

在这里插入图片描述

Code结构

Code attribute的作用是保存该方法的结构

在这里插入图片描述

  • attribute_length 表示attribute所包含的字节数,不包含attribute_name_index和attribute_length字段。
  • max_stack表示这个方法运行的任何时刻所能达到的操作数栈的最大深度
  • max_locals表示方法执行期间创建的局部变量的数目,包含用来表示传入的参数的局部变量
  • code_length表示该方法所包含的字节码的字节数以及具体的指令码
  • 具体字节码即是该方法被调用时,虚拟机所指向的字节码
  • exception_table,异常表,这里存放的是处理异常的信息
  • 每个exception_table表项由start_pc,end_pc,handle_pc,catch_type组成
  • start_pc和end_pc表示在code数组中的从start_pc和end_pc处(包含start_pc,不包含end_pc)的指令抛出的异常由这个表项来处理
  • handler_pc表示处理异常的代码的开始处。catch_type表示会被处理的异常类型,它指向常量池里的一个异常类。当catch_type为0时,表示处理所有异常

附加属性

方法的附加属性:

  • LineNumberTable:这个属性用来表示code数组·中的字节码和Java代码行数之间的关系。这个属性可以用来在调试的时候定位代码执行的行数。

代码示例

示例1

public class MyTest1 {

    private int a = 1;

    public int getA() {
        return a;
    }

    public void setA(int a) {
        this.a = a;
    }
}
E:\IdeaProjects\jvm_lecture\target\classes>javap -verbose com/wangzhao/jvm/bytecode/MyTest1
Classfile /E:/IdeaProjects/jvm_lecture/target/classes/com/wangzhao/jvm/bytecode/MyTest1.class
  Last modified 2019-7-21; size 497 bytes
  MD5 checksum 5fea812ebd710c6be8d438b7e0959a03
  Compiled from "MyTest1.java"
public class com.wangzhao.jvm.bytecode.MyTest1
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#20         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#21         // com/wangzhao/jvm/bytecode/MyTest1.a:I
   #3 = Class              #22            // com/wangzhao/jvm/bytecode/MyTest1
   #4 = Class              #23            // java/lang/Object
   #5 = Utf8               a
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/wangzhao/jvm/bytecode/MyTest1;
  #14 = Utf8               getA
  #15 = Utf8               ()I
  #16 = Utf8               setA
  #17 = Utf8               (I)V
  #17 = Utf8               (I)V
  #18 = Utf8               SourceFile
  #19 = Utf8               MyTest1.java
  #20 = NameAndType        #7:#8          // "<init>":()V
  #21 = NameAndType        #5:#6          // a:I
  #22 = Utf8               com/wangzhao/jvm/bytecode/MyTest1
  #23 = Utf8               java/lang/Object
{
  public com.wangzhao.jvm.bytecode.MyTest1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: iconst_1
         6: putfield      #2                  // Field a:I
         9: return
      LineNumberTable:
        line 7: 0
        line 10: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  this   Lcom/wangzhao/jvm/bytecode/MyTest1;

  public int getA();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field a:I
         4: ireturn
      LineNumberTable:
        line 13: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/wangzhao/jvm/bytecode/MyTest1;
  public void setA(int);
    descriptor: (I)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: iload_1
         2: putfield      #2                  // Field a:I
         5: return
      LineNumberTable:
        line 18: 0
        line 19: 5
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       6     0  this   Lcom/wangzhao/jvm/bytecode/MyTest1;
            0       6     1     a   I
}
SourceFile: "MyTest1.java"

通过javap命令,我们对MyTest1的class文件解析可得到如下结果:
在这里插入图片描述

在这里插入图片描述在这里插入图片描述
通过常量池,我们可以找到这个类拥有哪些字段,方法以及父类和接口的全限定类名。需要注意的是,常量池中的索引是从1开始,并没有使用0是因为0表示不引用任何一个常量。

在这里插入图片描述

可以看到==对于类中实例变量的赋值,是在构造方法中完成的。==同时编译器为我们自动增加了一个构造方法。

在这里插入图片描述
对于getA()方法,我们并没有传入参数,但是参数个数却为1,这是因为对于Java类中的每一个实例方法(非static)方法,其在编译后所生成的字节码当中,方法参数的数量总是会比源代码中方法参数的数量多一个(this),它位于方法的参数的第一个参数位置处;这样,我们就可以在java的实例方法中使用this来去访问当前对象的属性以及其他方法。

示例2

public class MyTest2 {

    String str = "Welcome";

    private int x = 5;

    public static Integer in = 10;

    public static void main(String[] args) {
        MyTest2 myTest2 = new MyTest2();
        myTest2.setX(8);
        in = 20;
    }

    public void setX(int x) {
        this.x = x;
    }

    private static void test(String str){
        synchronized (str){
            System.out.println("hello world");
        }
    }

}

在这里插入图片描述

可以看到synchronized与两个指令相关联,分别为minitorenter以及monitorexit。
minortorenter的作用是:执行该指令的线程尝试获取相关对象的监视器的所有权,如果监视器的计数为0,则线程进入监视器并将其计数设置为1。如果该线程已经拥有该对象的所有权,则监视器的计数加1(可重入)。而其他线程将阻塞,直到该对象的监视器计数为1。
minortorexit的作用是:拥有当前对象监视器所有权的线程递减关联对象的监视器计数,直到为0,当前线程退出监视器。

在这里插入图片描述
对于静态的字段,编译器会生成一个静态构造代码块,完成静态成员变量的赋值。

示例3

public class MyTest3 {

    public void test(){
        try {
            InputStream is = new FileInputStream("test.txt");
            ServerSocket serverSocket = new ServerSocket(9999);
        }catch (FileNotFoundException ex){

        }catch (IOException ex){

        }catch (Exception ex){

        }finally {
            System.out.println("finally");
        }
    }

}

在这里插入图片描述
在这里插入图片描述

可以看到对于异常的处理流程是通过异常表,罗列出所有可能的路径。

当异常处理存在finally语句块时,现代化的JVM采取的处理方式是将finally语句块的字节码拼接到每一个catch块后面,换句话说,程序中存在多少个catch块,就会在每一个catch块后面重复多少个finally语句块的字节码。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值