命令行查看字节码文件、字节码运行流程案例分析

目录

命令行查看字节码文件

字节码运行流程


命令行查看字节码文件

class B{
	private int a = 1234;
	static long c = 1111;
	public long test(long num) {
		long ret = this.a + num + c ;
		return ret ;
	}
}
public class A {
	private B b = new B() ;
	public static void main(String[] args) {
		A a = new A();
		long num = 4321;
		long ret = a.b.test(num) ;
		System.out.println(ret);
	}
}

当虚拟机遇到一条 new 指令时,首先会检查这个指令的参数能否在常量池中定位一个符号引用。然后检查这个符号引用的类字节码是否加载、解析和初始化。如果没有,将执行对应的类加载过程。

拿我们上面的代码来说,执行 A 代码,在调用 private B b = new B() 时,就会触发 B 类的加载。

A 和 B 会被加载到元空间的方法区,进入 main 方法后,将会交给执行引擎执行。这个执行过程是在栈上完成的,其中有几个重要的区域,包括虚拟机栈、程序计数器等。

使用下面的命令编译源代码 A.java。

javac -g:lines -g:vars A.java

这将强制生成 LineNumberTable 和 LocalVariableTable。

使用如下代码查看字节码:

javap -p -v A.class
javap -p -v B.class

 

Test执行过程

  public long test(long);
    descriptor: (J)J
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=4, locals=5, args_size=2
         0: aload_0
         1: getfield      #2                  // Field a:I
         4: i2l
         5: lload_1
         6: ladd
         7: getstatic     #3                  // Field c:J
        10: ladd
        11: lstore_3
        12: lload_3
        13: lreturn
      LineNumberTable:
        line 6: 0
        line 7: 12
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      14     0  this   Lcn/ren/demo/B;
            0      14     1   num   J
           12       2     3   ret   J

首先,注意 stack 字样,它此时的数值为 4,表明了 test 方法的最大操作数栈深度为 4。JVM 运行时,会根据这个数值,来分配栈帧中操作栈的深度。

相对应的,locals 变量存储了局部变量的存储空间。它的单位是 Slot(槽),可以被重用。其中存放的内容,包括:

  • this
  • 方法参数
  • 异常处理器的参数
  • 方法体中定义的局部变量

args_size 就比较好理解。它指的是方法的参数个数,因为每个方法都有一个隐藏参数 this,所以这里的数字是 2。

字节码运行流程

main 线程会拥有两个主要的运行时区域:Java 虚拟机栈和程序计数器。其中,虚拟机栈中的每一项内容叫作栈帧,栈帧中包含四项内容:局部变量报表、操作数栈、动态链接和完成出口。

(1)0: aload_0

把第 1 个引用型局部变量推到操作数栈,这里的意思是把 this 装载到了操作数栈中。

对于 static 方法,aload_0 表示对方法的第一个参数的操作。

(2)1: getfield      #2

将栈顶的指定的对象的第 2 个实例域(Field)的值,压入栈顶。#2 就是指的我们的成员变量 a。

#2 = Fieldref           #6.#27         // B.a:I
...
#6 = Class             #29           // B
#27 = NameAndType       #8:#9         // a:I


(3)i2l

将栈顶 int 类型的数据转化为 long 类型,这里就涉及我们的隐式类型转换了。图中的信息没有变动。

 

(4)lload_1

将第一个局部变量入栈。也就是我们的参数 num。这里的 l 表示 long,同样用于局部变量装载。你会看到这个位置的局部变量,一开始就已经有值了。

(5)ladd

把栈顶两个 long 型数值出栈后相加,并将结果入栈。

(6)getstatic #3

根据偏移获取静态属性的值,并把这个值 push 到操作数栈上。

(7)ladd

再次执行 ladd。

(8)lstore_3

把栈顶 long 型数值存入第 4 个局部变量。

还记得我们上面的图么?slot 为 4,索引为 3 的就是 ret 变量。

(9)lload_3

正好与上面相反。上面是变量存入,我们现在要做的,就是把这个变量 ret,压入虚拟机栈中。

(10)lreturn

从当前方法返回 long。

到此为止,我们的函数就完成了相加动作,执行成功了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值