第二章 JVM 编译

第一章 JVM 概述
第二章 JVM 编译
第三章 JVM 类结构
第四章 JVM 类加载机制

1. javac 编译 和 javap 查看 class 文件

本章描述的编译是指使用 JDK 自带的 java 工具把源代码编译成 class 文件,重点关注如何把 java 语言编译成 jvm 指令。
在这里插入图片描述

用户可以使用 javap 工具把二进制的 class 文件转换成用户可以看懂的 jvm 指令文本,来方便我们理解编译后的 class 文件,-p 参数会展示私有的方法的属性,-v 参数会展示尽可能多的信息。示例:javap -v -p HelloWorld。如果你使用了编译器 idea,可以使用 jclasslib 插件来查看 class 文件信息。

2. 指令格式

指令的格式:<index> <opcode> [ <operand1> [ <operand2>... ]] [<comment>]
index 是指从方法开始的位置到当前指令的偏移量
opcode 是指操作码
operand 是指操作数,操作码的参数
comment 由编译器或者用户生成的注释

以下代码对常用的 Java 语法做了示例,可以通过方法名去找对应的字节码实现。字节码实现基本可以和方法一一对应。

3. 如何学习和理解编译后字节码

3.1 对象创建的例子

3.1.1 Java 代码

// 新建类实例、操作数栈相关指令
JavaCompiler compiler = new JavaCompiler();

3.1.2 编译后的使用 javap 查看的字节码

0: new           #2             // class org/compiler/JavaCompiler
3: dup
4: invokespecial #3             // Method "<init>":()V
7: astore_1
8: aload_1

3.1.3 指令的执行

3.1.3.1 第一条指令 new

参照指令格式 0: new #2 // class org/compiler/JavaCompiler,0 代表当前指令在方法中的偏移量是0,代表第一条指令,new是操作符也就是是指令名称,表示当前指令用来创建对象实例,#2是指令的操作数即参数,# 代表内容需要从字节码的常量池 Constant poll中获取,常量池中 #2 对应的内容为 #76 对应的内容 org/compiler/JavaCompiler代表类的全限定名,也就是要创建对象的类型。最后一列的 comment 也帮我们标明了实际的值。执行完这条指令,JVM 就会创建一个 JavaCompiler实例,并把它放入操作数栈中。

Constant pool:
    #1 = Methodref          #24.#75       // java/lang/Object."<init>":()V
    #2 = Class              #76           // org/compiler/JavaCompiler
    #76 = Utf8               org/compiler/JavaCompiler
3.1.3.2 第二条指令 dup

执行完第二条指令 dup,在操作数栈中拷贝栈顶元素并推入栈内,此时本内有两个 this。

3.1.3.3 第三条指令 invokespecial

执行第三条指令 invokespecial #3会调用对象的 init方法并做初始化,需要从栈内弹出一个 this 参数,但是此方法无返回值,此时栈内还剩一个 this。

3.1.3.4 第四条指令 astore_1

执行第四条指令 astore_1把弹出一个操作数栈元素,并保存在本地变量 1 中。

3.1.3.5 第五条指令 aload_1

执行第五条指令 aload_1又把本地变量 1 的值推入操作数栈中供后面的方法使用。

指令执行后操作数栈变化

4. 关键知识点

基础的指令知识请参考Compiling for the Java Virtual Machine

  1. 新建类实例的指令比构造方法中的指令多了一个 dup指令,dup 是操作数栈的指令,它不需要关心数据的类型,作用是拷贝一份和当前栈顶元素一模一样的数据并推入栈内,作用是在对象初始化的时候为初始化后的 aload_n 提供 this 操作数,栈中 this 被 invokeSpecial init() 拿去执行,初始化方法没有返回值,如果不 dup 一个栈中就没有 this 了。
  2. 方法调用和静态方法调用的参数不一样,方法调用需要带上 this 作为第一个参数,参数个数和本地变量的个数都比静态方法多一个
  3. 同步关键字加在方法上,JVM 会在执行方法的 invoke 和 return 指令的时候判断方法上是否有标志 ACC_SYNCHRONIZED 并做相应的处理,确保方法不管是正常还是异常退出都能保证线程安全;同步代码块通过 moniterenter 和 monitorexist 指令实现,同时方法中会生成异常表,当方法中任务异常发生时都会执行成对的 monitorexit 指令来保证当前线程能从监视器中正常退出。
  4. switch 指令有两种实现方式,JVM 会对 switch 值稀疏的采用 lookuptable 实现方式(本例中使用此方式),对 switch 值紧凑的采用 tableswitch 实现方式。tableswitch 方式更高效,但是如果值稀疏字节码文件会很大。可以参考 Difference between JVM’s LookupSwitch and TableSwitch?

5. 代表性的 Java 代码,覆盖了常用的语法

public class JavaCompiler {

    private static final Integer LIMIT_100 = 100;
    private static final String FINALLY = "finally";

    public static void main(String[] args) {
        try {
            // 访问运行时常量池
            // 新建类实例、操作数栈相关指令
            JavaCompiler compiler = new JavaCompiler();
            // 调用方法、接收参数
            compiler.setCount(0);
            // 同步代码块
            int size = compiler.synchronizedBlockAddTwoInteger(compiler.addToLimit(), LIMIT_100);
            // 数组
            int[] array = new int[size];
            final int mod = size / LIMIT_100;
            int switchResult;
            // switch
            switch (mod) {
                case 0: switchResult = 0; break;
                case 1: switchResult = 1; break;
                case 2: switchResult = 2; break;
                case 5: switchResult = 5; break;
                case 10: switchResult = 10; break;
                case 20: switchResult = 20; break;
                case 30: switchResult = 30; break;
                default: switchResult = 99;
            }
            // 调用静态方法, 接收参数,同步方法
            System.out.println("result:" + compiler.synchronizedAddTwoInteger(JavaCompiler.staticAddTwoInteger(switchResult,mod),LIMIT_100));
        } catch (Exception e) {
            // 异常处理指令
            System.out.println(e.getMessage());
        } finally {
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值