首页 > 其他分享 >从栈帧看字节码是如何在JVM中进行流转的

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

时间:2023-04-23 10:38:20浏览次数:28  
标签:从栈 ladd 字节 局部变量 long num 码是 JVM test


从栈帧看字节码是如何在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的下载地址:https://github.com/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表示对方法的第一个参数的操作。

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

1: getfield #2

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

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

4: i2l

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

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

5: lload_1

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

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

6: ladd

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

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

7: getstatic #3

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

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

10: ladd

再次执行ladd。

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

11: lstore_3

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

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

这里为什么要把栈顶的变量存入局部变量表中,又取出来入栈呢,为什么会有这种多此一举的操作?原因就在于我们定义了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。

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

13: lreturn

从当前方法返回long。

更多精彩内容关注本人公众号:架构师升级之路

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


标签:从栈,ladd,字节,局部变量,long,num,码是,JVM,test
From: https://blog.51cto.com/u_6784072/6216452

相关文章

  • jvm之强软弱虚引用
    强软弱虚引用在java中,除了基本数据类型的变量外,其他所有的变量都是引用类型,指向堆上各种不同的对象。在jvm中,除了我们常用的强引用外,还有软引用、弱引用、虚引用,这四种引用类型的生命周期与jvm的垃圾回收过程息息相关。那么这四种引用类型有什么区别?具体使用场景是什么?所有引用类型......
  • jvm之垃圾收集器
    垃圾收集器先看下图中HotSpot虚拟机所包含的收集器:图中展示了9种作用于不同分代的收集器,如果两个收集器之间存在连线,则说明它们可以搭配使用。虚拟机所处的区域则表示它是属于新生代还是老年代收集器。新生代收集器:Serial、ParNew、ParallelScavenge老年代收集器:CMS、SerialOld、......
  • jvm之垃圾回收算法
    垃圾回收算法哪些内存需要回收jvm的内存模型中将内存划分为程序计数器、虚拟机栈、本地方法栈、堆、方法区。其中程序计数器、虚拟机栈、本地方法栈属于线程私有的内存空间,与线程的生命周期保持一致,不需要手动回收内存。方法区中存放的是类的结构信息,对方法区的回收其实就是对类进......
  • jvm之线程上下文加载器与SPI
    线程上下文加载器线程上下文类加载器(ThreadContextClassLoader,简称TCCL)是从JDK1.2开始引入的。类java.lang.Thread中的方法getContextClassLoader()和setContextClassLoader(ClassLoadercl)用来获取和设置线程的上下文类加载器。如果没有通过setContextClassLoader(ClassLoader......
  • jvm如何打破双亲委托机制
    打破双亲委托机制重写父类ClassLoader的loadClass方法packagecom.morris.jvm.classloader;publicclassBreakDelegateClassLoaderextendsMyClassLoader{@OverrideprotectedClass<?>loadClass(Stringname,booleanresolve)throwsClassNotFoundException{......
  • JVM内存模型
    JVM内存模型JVM的内存模型也就是JVM中的内存布局,不要与java的内存模型(与多线程相关)混淆。下图是jdk8jvm内存模型图:程序计数器程序计数器是当前线程所执行的字节码的行号指示器。JVM支持多个线程同时运行,每个线程都会根据CPU时间片来回切换,那么如果当前线程获得时间片了,怎么知道它......
  • Java虚拟机之JVM工具监控调优
    我是攻城师(woshigcs)前几篇我们学习了,JVM里面的运行结构,GC算法,以及各种垃圾收集器的优劣点,那么本篇我们来看下如何使用一些虚拟机性能监控工具,来监控和快速处理故障,当JVM出现一些故障时,我们通常从如下的几个方面进行着手分析,包括运行日志,异常堆栈,GC日志,线程快照(threaddump/javacor......
  • JDK,JRE,JVM之间的关系
    JDK,JRE,JVM三者之间的关系JDK=JRE+开发工具集(例如javac编译工具等)JRE=JVM+JavaSE标准类库JDK(Java开发工具包)JRE(Java运行环境)JVM(Java虚拟机)......
  • 一文回顾JVM
     ......
  • 从原理聊JVM(一):染色标记和垃圾回收算法
    作者:京东科技 康志兴1JVM运行时内存划分1.1运行时数据区域•方法区属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。运行时常量池,属于方法区的一部分,用于存放编译期生成的各种字面量和符号引用。JDK1.8之前,Hotspot虚拟机对方法区......