运行时内存结构
在了解jvm指令之前,需要先了解java虚拟机运行时内存的结构,根据jvm规范(java se8),运行时数据区给每个线程分配了不同的区域,比如PC寄存器、JVM栈,本地方法栈,线程共有的部分有堆与方法区。
栈上每个函数调用都有一个帧作为对应数据结构,每个帧都包括局部变量表与操作数栈,这部分在编译的时候就可以确定了,局部变量表用来保存方法中的局部变量,以及方法参数。当一个方法刚开始执行的时候,操作数栈是空的,在方法执行过程中,会有各种字节码指令往操作数栈中写入和取出数据,也就是入栈和出栈操作。
https://www.cnblogs.com/jiuxing/p/13899711.html
可以使用jclasslib来看到方法的局部变量表:
由于方法是static方法,所以局部变量表的第一个元素并不是this;
非static方法:
jvm指令执行
根据字节码解释器的工作流程:
do {
atomically calculate pc and fetch opcode at pc;
if (operands)
fetch operands;
execute the action for the opcode;
} while (there is more to do);
指令操作的对象有部分就是局部变量表上的数据,然后执行方法会在栈上进行计算,这种方式可以描述为基于栈的字节码解释执行。
比如一个简单的执行加法的函数:
public int add(int i, int j) {
return i+j;
}
指令
0 iload_1
1 iload_2
2 iadd
3 ireturn
iload_1 iload_2 分别把两个局部变量表的内容放在栈上,然后执行iadd操作,取出栈顶两个元素,相加后放回栈顶,最后执行ireturn,返回栈顶的int
每个栈帧都包含了一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态链接。
static abstract class Human {
abstract void sayHello();
}
static class Man extends Human {
@Override
void sayHello() {
System.out.println("Man say hello!");
}
}
static class Woman extends Human {
@Override
void sayHello() {
System.out.println("Woman say hello!");
}
}
private static void testHumanSayHello() {
Human man = new Man();
Human woman = new Woman();
man.sayHello();
woman.sayHello();
man = new Woman();
man.sayHello();
}
编译出的字节码为
0 new #3 <com/sankuai/example/rasp/pigeon/test/starter/util/ASMDemo$Man>
3 dup
4 invokespecial #4 <com/sankuai/example/rasp/pigeon/test/starter/util/ASMDemo$Man.<init> : ()V>
7 astore_0
8 new #5 <com/sankuai/example/rasp/pigeon/test/starter/util/ASMDemo$Woman>
11 dup
12 invokespecial #6 <com/sankuai/example/rasp/pigeon/test/starter/util/ASMDemo$Woman.<init> : ()V>
15 astore_1
16 aload_0
17 invokevirtual #7 <com/sankuai/example/rasp/pigeon/test/starter/util/ASMDemo$Human.sayHello : ()V>
20 aload_1
21 invokevirtual #7 <com/sankuai/example/rasp/pigeon/test/starter/util/ASMDemo$Human.sayHello : ()V>
24 new #5 <com/sankuai/example/rasp/pigeon/test/starter/util/ASMDemo$Woman>
27 dup
28 invokespecial #6 <com/sankuai/example/rasp/pigeon/test/starter/util/ASMDemo$Woman.<init> : ()V>
31 astore_0
32 aload_0
33 invokevirtual #7 <com/sankuai/example/rasp/pigeon/test/starter/util/ASMDemo$Human.sayHello : ()V>
36 return
可以看到在17与21行执行sayHello方法时,都是使用的invokevirtual,根据jvm虚拟机规范对指令的说明,执行invokevirtual,会把对象放在操作数栈的顶部,执行方法时先找到这个objectref对应的类C(从持有的字符串常量池的引用中查找),如果C中包含这个方法的重写方法,就调用,否则从下往上一次寻找类C的父类有无这个方法,如果有就调用。对于接口来说,需要找到一个方法实现并且方法描述里不包含abstract。
jvm的指令规范:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5
参考
https://tobebetterjavaer.com/jvm/how-jvm-run-zijiema-zhiling.html
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5
标签:局部变量,sayHello,指令,jvm,执行,方法 From: https://www.cnblogs.com/cyyyyx/p/17647095.html