关于jvm:看这一篇学会读jvm字节码

37次阅读

共计 3157 个字符,预计需要花费 8 分钟才能阅读完成。

装置插件

在 setting-Plugins 搜寻 jclass 装置下图中的插件。

查看字节码

1、首先咱们写一个简略的 java 代码来察看其字节码:

public class TestExep {public static void main(String[] args) {new TestExep().f();}
 public int f(){
     int a = 1+3;
     a++;
     return a;
 }
}

2、运行后,点击源程序 -》点击 view->Show Bytecode With jclasslib

3、咱们当初看到上图左边一块就是 jclasslib 解析进去的字节码:
次要有上面几块内容:

1) General Information:

类的一些根本信息,次要信息如下:Major Version = 52 示意编译此类文件的 jdk 版本 1.8
    Constant pool count = 27 是常量池大小
    Access Flag = 0x0021 [public] 示意类的拜访修饰符
    This class : 本类
    Super class : 父类
    Interfaces count = 0 实现接口数 0(类中没有实现任何接口)Fields count = 0 属性数(没有定义任何属性)Methods count = 3 这是办法数(类中只写了两个办法,为什么有 3 个办法呢,因为还有一个是类的默认构造方法 <init>)

2) Constant pool: 常量池

咱们看一下 Oracle 官网文档对常量池的形容:`constant_pool[]

The constant_pool is a table of structures (§4.4) representing various string constants, class and interface names, field names, and other constants that are referred to within the ClassFile structure and its substructures. The format of each constant_pool table entry is indicated by its first “tag” byte.

The constant_pool table is indexed from 1 to constant_pool_count – 1.
常量池是一个构造体表,这些构造体示意字符串常量、类及接口名、属性名和类文件构造及子结构援用的其余常量,每个 constant_pool 表项的格局由其第一个“tag”字节示意。常量池表项索引从 1 到 constant_pool_count-1(第 0 号不必)

从这段形容咱们能够晓得常量池不只放咱们在程序中定义的常量,还有编译后的类相干信息。

3) Intefaces 接口
4) Fields 属性
5) Methods 办法:

办法外面就是编译后的字节码,Code: 编译后的字节码
 Code 上面有两个货色:
 LineNumberTable : 行号表,是用来记录每行代码的行号,不便代码报错的时候打印出行号,为什么须要行号表呢?因为源文件一行代码被 jvm 编译后的生成的字节码可能有多行,索引须要用行号表记录编码后的代码在源文件的行号。LocalVariableTable: 局部变量表,寄存办法运行时的局部变量,这是咱们这次解读代码比拟重要的货色。

办法 f 的字节码

咱们点击办法 f 的 Code, 能够看到如下的字节码:

0 iconst_4
1 istore_1
2 iinc 1 by 1
5 iload_1
6 ireturn

当初看到这一坨代码还不晓得是什么意思,咱们须要先理解一下 Java 运行时的内存区及这些代码指令的含意。

java 运行时数据区

1、咱们晓得 java 运行的内存被分为两个局部一个是堆区,一个是栈区。
栈区是线程公有的,线程公有的局部包含 3 个货色:
PC:程序计数器
Native Method stack: 本地办法栈
JVM stack: jvm 栈,每次调用 Java 办法的对应一个栈帧
2、当咱们的程序开始运行的时候,对应一个线程,当调用 f 办法的时候,会在这个线程的 jvm 栈外面生成一个栈帧记录办法运行时的数据。
那么运行时蕴含哪些数据呢:

1.  Local Variable Table 局部变量表
2.  Operand Stack 操作数栈
3.  Dynamic Linking 
4.  return address a() -> b(),办法 a 调用了办法 b, b 办法的返回值放在什么中央

其中,局部变量表和操作数栈是两个比拟重要的货色,帮忙咱们了解 jvm 指令。

指令集

jvm 的指令集是基于栈,意思是任何 jvm 指令执行的操作数都是在操作数栈弹出来的,计算后的后果也压到外面。
所以咱们看到的指令全是没有操作数的,因为默认从栈里取操作数。
如果计算的后果须要在前面应用,那么须要把后果寄存到局部变量表中。
例如:
iload 这条指令的 jvm 官网文档形容(https://docs.oracle.com/javase/specs/jvms/se15/html/jvms-6.html#jvms-6.5.iload
)

Description
The _index_ is an unsigned byte that must be an index into the local variable array of the current frame ([§2.6](https://docs.oracle.com/javase/specs/jvms/se15/html/jvms-2.html#jvms-2.6 "2.6. Frames")). The local variable at _index_ must contain an `int`. The _value_ of the local variable at _index_ is pushed onto the operand stack.
index 是无符号 byte 类型,是以后栈帧局部变量表里的索引,index 将以后栈帧局部变量表的的 index 地位变量的值压到操作数栈里。

办法 f 代码解读

首先咱们来看一下,当办法 f 被调用是栈帧的状况:
首先调用 main 有个 Main 办法的栈帧,而后调用 f 办法,又有一个 f 办法的栈帧。

上面咱们看一下办法 f 的字节码

0 iconst_4
1 istore_1
2 iinc 1 by 1
5 iload_1
6 ireturn

首先咱们须要一个局部变量表,一个操作数栈:

1、iconst_4: 示意把整数常量 4 压入操作数栈(然而咱们的程序外面没有常量 4,只有 1 +3 编译器间接把后果计算出来,这样程序运行的时候就不须要计算了), 此时操作数栈栈顶元素是 4
这条指令执行后局部变量表(LVT)和操作数栈 (Oprand) 的状况:

2、istore_1: 把操作数栈顶元素出栈并存入局部变量表索引 1 地位,此时 a =4, LVT 和 Oprand 状况:

3、iinc 1 by 1: 把局部变量表索引 1 地位的数 +1,即 a ++, 此时 a =5,LVT 和 Oprand 状况:

4、iload_1: 将局部变量表索引 1 地位的数压入操作数栈,此时操作数栈有一个值,LVT 和 Oprand 状况如下:

5、ireturn: 将操作数栈顶元素返回给 main 办法, 办法返回后,整个办法栈帧都会被删除。

总结

以上展现了一个简略的 java 程序的字节码浏览办法,要害是了解 java 的字节码指令基于栈编程模型,这样就好了解了,jvm 指令在 jvm 标准中都能够查到,只有了解了这个,碰到不意识的指令,间接去 jvm 标准查问,咱们就能够轻松看懂字节码。
咱们看字节码能够看到 jvm 编译 class 文件过程中做的一些优化,比方常量运算间接在编译阶段就做了,执行阶段不须要再解决。
看字节码能够加深咱们对 Java 的了解。
更重要的是 jvm 跟 java 无关,很多语言都能够在 jvm 上执行,比方 scala, jypthon, kotlin 等等,只有一门语言写的程序能够按 jvm 标准编译成.class 文件就能够跑在 java 虚拟机上。

正文完
 0