从栈帧看字节码是如何在JVM中进行流转的

作者 : 开心源码 本文共2433个字,预计阅读时间需要7分钟 发布时间: 2022-05-13 共240人阅读

从栈帧看字节码是如何在JVM中进行流转的
我们都知道java文件需要编译成class文件,而后jvm负责加载并运行class文件,那么字节码文件长什么样子?字节码又是怎样执行的?

工具详情
javap
javap是JDK自带的查看字节码的工具。

javap的使用方法如下:

$ javac Demo.java$ javap -p -v Demo

javap命令打印的文件内容有时候过多,可以使用javap -p -v Demo >> Demo.javap将内容追加至文本文件中,再用文本工具打开分析。

有时候class文件中没有生成LineNumberTable或者LocalVariableTable,可以在编译时使用下面的参数强制生成:

javac -g:lines 强制生成LineNumberTable。
javac -g:vars 强制生成LocalVariableTable。
javac -g 生成所有的debug信息。

LocalVariableTable就是栈帧中的局部变量表。

LineNumberTable形容源码行号与字节码行号(字节码偏移量)之间的对应关系,有了这些信息,在debug时,就能够获取到发生异常的源代码行号。

jclasslib
假如你不太习惯使用命令行的操作,还可以使用jclasslib,jclasslib是一个图形化的工具,能够更加直观的查看字节码中的内容。它还分门别类的对类中的各个部分进行了整理,非常的人性化。同时,它还提供了Idea的插件,你可以从plugins中搜索到它。

假如你在其中看不到少量诸如LocalVariableTable的信息,记得在编译代码的时候加上我们上面提到的这些参数。

jclasslib的下载地址: ingokegel/jclasslib

Demo.java
下面的java代码就是后面要分析的字节码对应的源文件:

public class Demo {    private int a = 1111;    static long C = 2222;    public long test(long num) {        long ret = this.a + num + C;        return ret;    }    public static void main(String[] args) {        new Demo().test(3333);    }}

test方法的执行过程
Code区域详情
test方法同时使用了成员变量a、静态变量C,以及输入参数num。我们此时说的方法执行,内存其实就是在虚拟机栈上分配的。下面这些内容,就是test方法的字节码。

  public long test(long);    descriptor: (J)J    flags: 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 7: 0        line 8: 12

说明:

stack=4:表明了test方法的最大操作数栈深度为4。JVM运行时,会根据这个数值,来分配栈帧中操作栈的深度。
locals=5:局部变量的存储空间大小,它的单位是Slot(槽),可以被重用。其中存放的内容包括:this、方法参数、异常解决器的参数、方法体中定义的局部变量。
args_size=2:方法的参数个数,由于每个实例方法都有一个隐藏参数this(静态方法没有this),所以这里的数字是2。
字节码执行过程
0: aload_0
把第1个引用型局部变量推到操作数栈,这里的意思是把this装载到了操作数栈中。

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

image.png

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

image.png

4: i2l
将栈顶int类型的数据转化为long类型,这里就涉及我们的隐式类型转换了。

image.png

5: lload_1
将第一个局部变量入栈,也就是我们的参数num,这里的l表示long。

image.png

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

image.png

7: getstatic #3
根据偏移获取静态属性的值,并把这个值push到操作数栈上,也就是静态变量C。

image.png

10: ladd
再次执行ladd。

image.png

11: lstore_3
把栈顶long型数值存入第4个局部变量,一个long和double类型会占用2个slot。

image.png

这里为什么要把栈顶的变量存入局部变量表中,又取出来入栈呢,为什么会有这种多此一举的操作?起因就在于我们定义了ret变量。JVM不知道后面还会不会用到这个变量,所以只好傻瓜式的顺序执行。

为了看到差异,我们可以把代码略微改动一下,直接返回:

    public long test(long num) {        return this.a + num + C;    }

对应的字节码如下:

  public long test(long);    descriptor: (J)J    flags: ACC_PUBLIC    Code:      stack=4, locals=3, 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: lreturn      LineNumberTable:        line 7: 0

12: lload_3*
将第3个局部变量入栈,也就是我们的参数num,这里的l表示long。

image.png

13: lreturn
从当前方法返回long。

更多精彩内容关注公众号:慕容千语

原文链接:https://blog.csdn.net/u022812849/article/details/107411679

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 从栈帧看字节码是如何在JVM中进行流转的

发表回复