由JVM(一)简介对运行时数据区有了大概的了解,下面详细介绍虚拟机栈(Java Virtual Machine stack)。

每个JVM线程都有一个JVM栈,栈中保存着栈帧(Frame)。
一、工具
编译如下代码:
package com.niiiico.wgdemo;
public class Test {
public int testAdd() {
int a = 10;
int b = 20;
int c = a + b;
return c;
}
}
1、javap命令
将java文件编译成class后,通过javap -v Test.class即可查看类相关的信息。
$ javap -v Test.class
Classfile /D:/workplace/WGDemo/app/build/intermediates/javac/debug/classes/com/niiiico/wgdemo/Test.class
Last modified 2022-3-28; size 417 bytes
MD5 checksum bf610cf1af0aaa5f64ff7104523a160d
Compiled from "Test.java"
public class com.niiiico.wgdemo.Test
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
// 常量池
Constant pool:
#1 = Methodref #3.#19 // java/lang/Object."<init>":()V
#2 = Class #20 // com/niiiico/wgdemo/Test
#3 = Class #21 // java/lang/Object
#4 = Utf8 <init>
#5 = Utf8 ()V
#6 = Utf8 Code
#7 = Utf8 LineNumberTable
#8 = Utf8 LocalVariableTable
#9 = Utf8 this
#10 = Utf8 Lcom/niiiico/wgdemo/Test;
#11 = Utf8 testAdd
#12 = Utf8 ()I
#13 = Utf8 a
#14 = Utf8 I
#15 = Utf8 b
#16 = Utf8 c
#17 = Utf8 SourceFile
#18 = Utf8 Test.java
#19 = NameAndType #4:#5 // "<init>":()V
#20 = Utf8 com/niiiico/wgdemo/Test
#21 = Utf8 java/lang/Object
{
// 构造方法
public com.niiiico.wgdemo.Test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/niiiico/wgdemo/Test;
// testAdd方法
public int testAdd();
descriptor: ()I
flags: ACC_PUBLIC
Code:
// 操作数栈深2,本地变量表长度4(this、a、b、c)
stack=2, locals=4, args_size=1
0: bipush 10
2: istore_1
3: bipush 20
5: istore_2
6: iload_1
7: iload_2
8: iadd
9: istore_3
10: iload_3
11: ireturn
// 源码中的行号与字节码行号对应关系,如:源码中 int a = 10;字节码中在第0行。
LineNumberTable:
line 5: 0
line 6: 3
line 7: 6
line 8: 10
LocalVariableTable:
Start Length Slot Name Signature
0 12 0 this Lcom/niiiico/wgdemo/Test;
3 9 1 a I
6 6 2 b I
10 2 3 c I
}
SourceFile: "Test.java"
2、jclasslib Bytecode Viewer
(1)点击file -> setting -> Plugin -> Marketplace,搜索jclasslib插件,进行安装。

(2)书写java代码,点击Build -> Rebuild Project,重新编译。

(3)通过app -> build -> intermediates -> javac -> 包名,找到class文件。

(4)点击view -> Show Bytecode With Jclasslib查看class结构

(5)查看常量池,方法等结构。

二、栈帧
栈帧用来存储数据和部分结果、执行动态链接、方法返回和分发异常。栈帧包含:局部变量表、操作数栈、动态链接、方法返回地址。
对于活动线程,位于栈顶的帧是当前帧,对应的方法是当前方法。当前方法调用新的方法时,会创建新的栈帧,此时新的栈帧变为当前帧;方法返回时,如果有返回值,会将结果返回上一帧,同时该帧被销毁,上一帧变为当前帧。
1、局部变量表
每个帧拥都有一个数组来保存自己的局部变量,数组长度在编译时决定,具体大小可以在编译后的class文件中看到。
局部变量表以Slot(变量槽,32位)为单位,一个Slot可以保存boolean、byte、char、short、int、float、reference、returnAddress类型的数据,两个Slot可以保存long、double类型的数据。
如果执行的方法是实例方法,第0位用来传递对象本身的引用(this),其余位置用来表示其他参数。


通过jclasslib可以看到详细的局部变量表,如:this、a、b、c等。其中起始PC表示从第几行开始生效,长度表示生效行数。
2、操作数栈
每个帧都有一个后进先出的操作数栈,操作数栈的最大深度也是在编译时确定的。
帧创建时,操作数栈是空的,JVM通过指令向里存入或取出常量、变量的值。它也可以用来准备传递给方法的参数、接收方法的返回结果。

3、动态链接
动态链接指向当前方法所在类的运行时常量池。
我们知道方法区会存储每个类的信息,而类信息中有运行时常量池用来标记方法。当前方法如果需要调用其他方法的时候,可以通过动态链接从运行时常量池中找到对应的符号引用,然后将符号引用转换为直接引用,就能直接调用对应方法。
4、方法返回地址
存放调用方法的寄存器值。
方法结束有两种方式:正常执行完成、抛出异常。
正常返回时:将寄存器的值作为返回地址;如果方法有返回值,也会向调用方法返回一个值。
发生异常时:会去异常表中查询指令,如果没有查到,。
无论哪种方式退出,都会返回到该方法被调用的位置。
package com.niiiico.wgdemo;
import android.util.Log;
public class Test {
public Test() {
}
public void testAdd() {
try {
boolean var1 = true;
} catch (Exception var2) {
Log.v("AAAAA", var2.toString());
}
}
}
字节码:
0 iconst_3
1 istore_1
2 goto 16 (+14)
5 astore_1
6 ldc #3 <AAAAA>
8 aload_1
9 invokevirtual #4 <java/lang/Exception.toString : ()Ljava/lang/String;>
12 invokestatic #5 <android/util/Log.v : (Ljava/lang/String;Ljava/lang/String;)I>
15 pop
16 return
上述代码,查看class结构,可以看到异常表如下图所示:

表示字节码0-2(源码8-11)行可能会有异常,如果发生异常,跳转到字节码第5行(源码第9行)执行。

三、方法执行流程
下图展示了方法执行过程中,局部变量表、操作数栈、程序计数器的数据变化。

四、其他
1、空构造函数栈深

观察发现空构造函数的栈深是1,看如下字节码,发现调用了aload_0,表示将this装载到了操作数栈中,然后通过return将this返回。
0 aload_0
1 invokespecial #1 <java/lang/Object.<init> : ()V>
4 return
2、局部变量表slot复用
package com.niiiico.wgdemo;
import android.util.Log;
public class Test {
public void testAdd() {
int a = 1;
{
int b = a + 1;
Log.v("AAAAA", String.valueOf(b));
}
int c = a + 1;
}
}
如上述代码,this、a、b、c,四个变量,但是由于变量b的有效周期为6-15行,变量b、c类型相同,作用范围不重叠,从而产生复用。

网友评论