美文网首页
字节码和字节码指令入门学习

字节码和字节码指令入门学习

作者: 后山野鹤 | 来源:发表于2020-03-02 16:02 被阅读0次

学习完JVM的分区以及.class文件的加载机制,下面来学习下通过工具查看栈是如何操作字节码的。
抛出几个问题
1、怎么查看字节码?
2、字节码是什么样子的?
3、对象初始化后,字节码是如何被栈执行的?
让我一起动手来实践下,详细的分析一个java文件产生的字节码,并从栈帧层面看一下字节码的具体执行过程

工具介绍

javap

javap是JDK自带的反解析工具,它的作用是将.class文件解析成可读的文件格式。使用javap -p -v XXX.class -p参数:打印一些私有的字段和方法;-v参数:尽量多的打印一些信息
在stack Overflow上有个有意思的问题:在某个类中增加一行注释之后,为什么两次生成的.class文件,它们的md5是不一样的?
因为在javac命令中可以指定一些额外的内容输出到字节码。
常用的有

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

为了观察字节码,以上命令要熟练掌握

jclasslib

jclasslib是一个图形化的工具,能够直接查看字节码的内容。jclasslib下载地址

类加载和对象创建

首先,写一个简单的Java程序A.java。
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:"+ret);
}
}

类的初始化发生在类加载的过程中,那么对象的初始化呢?
对象的初始化常用的new 方法,还有其他的方法

Class 的newInstance

Constructor类的newInstance方法

反序列化

使用Object的clone方法

其中,后两个方法没有用到构造函数。
当虚拟机遇到一条new指令时,首先会检查指令的参数能否在常量池中定位一个符号引用。然后检查这个符号引用的类字节码是否加载、解析和初始化。如果没有,将执行对应的类加载。
在上面的代码中,执行过程中,在调用private B b = new B()时,会触发B类的类加载。
通过命令查看类B的test方法的字节码


test的code图

介绍下三个比较重要的值
1>stack 值是4 。表明test方法的最大操作数栈深度为4。JVM运行时,会根据这个值,来分配栈帧中操作栈的深度。
2>locals变量存储了局部变量的存储空间。它的单位是Slot槽,可以被重用。其中存放的内容包括:this、方法参数、异常处理器的参数和方法体中定义的局部变量。
3>args_size指的是方法的参数个数,每个方法都有一个隐藏的参数this,所有数值是2。

字节码执行过程

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


字节码详细图

我们的字节码指令,就是靠操作这些数据结构运行的。下面看下具体的字节码指令


字节码图

1)0:aload_0把第1个引用型局部变量推到操作数栈,这里的意思是把this装载到操作数栈中
2)1:getfield #2 将栈顶的指定的对象的第二个实例域(Field)的值,压入栈顶。#2指的是我们的成员变量a
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个局部变量。
9)lload_3 正好与上面相反。上面是变量存入,现在要做的就是把这个变量ret,压入虚拟栈中。
10) lreturn 从当前方法返回long。
至此,函数完成了相加动作,执行成功。JVM为我们提供了非常丰富的字节码指令。详细的字节码指令列表,可以参考下面链接 https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html

注意点

注意上面的第8步,首先把变量存放到了变量报表,然后又拿出这个值,把它入栈。为什么多此一举的操作呢?因为在于我们定义了ret变量,JVM不知道执行顺序,只好默认的顺序执行。
假如不生成变量 直接返回相加的值,这样就会很好理解了。由于栈的操作复杂度是O(1),对于我们的程序性能几乎没有影响,不必尽量少的定义成员变量,因此平常编码,还是要以可读性为首要任务。

小结

通过这次学习,掌握了基础的字节码指令对程序计数器、局部变量表、操作数栈等内容的影响,初步了解了Java字节码的文件格式。
希望可以建立一个运行时的全局动态图,在看到相关的opcode时,能够举一反三的思考背后对这些数据结构的操作。这样理解字节码指令,才能做到事半功倍。

相关文章

  • 字节码和字节码指令入门学习

    学习完JVM的分区以及.class文件的加载机制,下面来学习下通过工具查看栈是如何操作字节码的。抛出几个问题1、怎...

  • Java虚拟机-字节码指令

    1 字节码指令 Java字节码指令的执行离不开操作数栈,局部变量表,和常量池。 2 常量池 对于字节码指定来说,常...

  • Java 字节码指令

    字节码指令链接

  • JVM-06

    switch-case的字节码指令: Java代码如下: 字节码指令如下: 结论是:switch-case 语句 ...

  • 从底层入手,解析字节码增强和Btrace应用

    这篇文章聊下字节码和相关的应用。 1、机器码和字节码 机器码(machine code),学名机器语言指令,有时也...

  • Java并发机制的底层原理

    Java程序执行:Java代码→Java字节码→字节码被类加载器加载到JVM里,JVM执行字节码→转化为汇编指令在...

  • Java并发机制的底层实现

    Java代码编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令在...

  • synchronized的实现原理与应用

    Java代码在编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令...

  • 原子操作的实现原理

    Java代码在编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令...

  • volatile的实现原理与应用

    Java代码在编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令...

网友评论

      本文标题:字节码和字节码指令入门学习

      本文链接:https://www.haomeiwen.com/subject/sfmpkhtx.html