第8章 虚拟机字节码执行引擎

时间:2022-12-24 14:33:52

8.1. 概述

执行引擎是java虚拟机最核心的组成之一。 “虚拟机”是相对于“物理机”的概念,执行引擎在执行java代码的时候可能有解释执行和编译执行(通过即时编译器产生本地代码执行)。

8.2 运行时栈帧结构

栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构。栈帧包括 局部变量表、操作数栈、动态连接和方法返回地址等信息。

对于执行引擎来说,活动线程中,只有栈顶的栈帧是有效的。称为当前的栈帧(Current Stack Frame),栈帧所有关联的方法称为当前的方法(current Method)。

8.2.1 局部变量表

局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。在编译成class文件,方法属性max_locals数据项所需要分配的最大局部变量表的容量。

第8章 虚拟机字节码执行引擎

局部变量表的容量以变量槽(Variable Slot) 为最小单位。

一个Slot可以存放一个:boolean 、byte 、char 、 short、int、float、reference 或 returnAddress

连续两个Slot存放一个:long、double。

非static方法实例化,局部变量表中第0位索引的Slot默认是本对象(this)

局部变量表可以重复使用,所以会影响系统的垃圾回收。

代码1:注意main方法右键-》RunAs->Run Configuration..->Arguments标签->VM arguments,在框里填入 -verbose:gc

package demo;

public class Slot {

public static void main(String [] args) {
byte[] placeholder = new byte[64*1024*1024];
System.gc();
}
}

日志:

[GC (System.gc())  67533K->66160K(125952K), 0.0007933 secs]
[Full GC (System.gc()) 66160K->66068K(125952K), 0.0051875 secs]

显示没有回收,显然它自己也在其中当然不能清除

代码2.增加花括号隔离起来

package demo;

public class Slot {

public static void main(String [] args) {
{
byte[] placeholder = new byte[64*1024*1024];
}
System.gc();
}
}

日志

[GC (System.gc())  67533K->66176K(125952K), 0.0008035 secs]
[Full GC (System.gc()) 66176K->66068K(125952K), 0.0050225 secs]

还是没清除,原因是没人占坑,数据没有重写

代码3.定义一个局部变量去占坑

public class Slot {

public static void main(String [] args) {
{
byte[] placeholder = new byte[64*1024*1024];
}
int i=0;
System.gc();
}
}

日志:

[GC (System.gc())  67533K->66160K(125952K), 0.0010391 secs]
[Full GC (System.gc()) 66160K->532K(125952K), 0.0047491 secs]


可以看出变成532k。被清除一次。

8.2.2 操作数栈

操作数栈也常称为操作栈,它是一个先入后出(Last In First Out LIFO])栈。同局部变量表一样。

刚开始操作数栈是空的,在方法的执行过程中,会有各种字节码指令向操作数栈中写入和提取内容。上个局部变量表可以与下一个操作数栈共享区域。

Java虚拟机的解释执行引擎称为“基于栈的执行引擎”,其中所指的“栈”就是操作数栈。

8.2.3 动态连接

每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。

静态解析:类加载阶段或第一次使用的时候转化为直接引用。

动态连接:每一次运行期间转化为直接引用。

8.2.4 方法返回地址

正常完成出口 :遇到return

异常完成出口:本方法内没有对异常进行处理。

8.2.5 附加信息

其他补充信息,例如调试信息。一般会把动态连接、方法返回地址与其他附加信息全部归为一类称为栈帧信息。

8.3 方法调用

方法调用并不等于执行,确定调用那个方法,不涉及具体运行过程。

8.3.1 解析

在编译阶段就是已经确定调用那些方法,这个方法的调用称为解析(Resolution)

“”编译器可知,运行期不可变”  : 主要静态方法和私有方法

四条指令

  • invokestatic:调用静态方法
  • invokespecial 调用实例构造器<init>方法、私有方法和父类方法
  • invokevirtual: 调用所有的虚方法
  • invokeinterface:调用接口方法,在运行时再确定一个实现此接口的对象。

非虚方法 : 被final修饰的方法(虽然它使用invokevirtual进行调用)、invokestaticinvokespecial (在解析阶段确定唯一调用版本)

虚方法 : 除了非虚方法

静态解析示例

package demo;

public class StaticResolution {

public static void sayHello(){
System.out.println("hello world");

}
public static void main(String[] args) {
StaticResolution.sayHello();
}
}

查看命令

D:\workspace\aes\src\main\java\demo>javac StaticResolution.java

D:\workspace\aes\src\main\java\demo>javap -verbose StaticResolution

{
public demo.StaticResolution();
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

public static void sayHello();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: getstatic #2 // Field java/lang/System.out:Ljav
a/io/PrintStream;
3: ldc #3 // String hello world
5: invokevirtual #4 // Method java/io/PrintStream.prin
tln:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 6: 0
line 8: 8

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=0, locals=1, args_size=1
0: invokestatic #5 // Method sayHello:()V
3: return
LineNumberTable:
line 10: 0
line 11: 3
}
SourceFile: "StaticResolution.java"

可以看出 inovkestatic  (main方法)

分派可以调用静态或动态,根据宗量数分为单分派和多分派,两两组合静态单分派、静态多分派、动态单分派、动态多分派四种。

8.3.2 分派(多态)

1.静态分派  (重载)

public class StaticDispatch {

static abstract class Human{

}

static class Man extends Human {

}

static class Women extends Human{

}
public void sayHello(Human guy){
System.out.println("hello.guy");

}
public void sayHello(Man guy){
System.out.println("hello, gentleman");

}
public void sayHello(Women guy){
System.out.println("hello, lady");
}

public static void main(String[] args) {

Human man = new Man();
Human women = new Women();
StaticDispatch sr = new StaticDispatch();
sr.sayHello(man);
sr.sayHello(women);

}
}
日志:

hello.guy
hello.guy

因为Human是Static修饰。也就是在编译的时候就确认调用方法为Human为方法参数的方法,而不会实际类型方法。

当然它可以自动选择最合适的方法

import java.io.Serializable;

/**
* 方法优先级从上到下递减
* @author Administrator
*
*/
public class Overload {

public static void sayHello(char arg){
System.out.println("hello char");
}

public static void sayHello(int arg){
System.out.println("hello int");
}

public static void sayHello(long arg){
System.out.println("hello long");
}

public static void sayHello(Character arg){
System.out.println("hello Character");
}

public static void sayHello(Serializable arg){
System.out.println("hello Serializable");
}

public static void sayHello(Object arg){
System.out.println("hello Object");
}

public static void sayHello(char ... arg){
System.out.println("hello char ... ");
}

public static void main(String[] args){
sayHello('a');
}
}

类似:如果你最好的选择,你肯定会选择最好,不行其次、再其次、一直到你的底线。

2.动态分派 (重写)

public class DynamicDispatch {

static abstract class Human {
protected abstract void sayHell();
}
static class Man extends Human {

@Override
protected void sayHell() {
System.out.println("man say hello");

}

}

static class Women extends Human{

@Override
protected void sayHell() {
System.out.println("women say hello");

}

}
public static void main(String[] args) {
Human man = new Man();
Human women = new Women();
man.sayHell();
women.sayHell();
man = new Women();
man.sayHell();
}
}

结果:

man say hello
women say hello
women say hello

父类方法是抽象,子类需要实现。

3.单分派与多分派

方法的接收者与方法的参数统称为方法的宗量,单分派是根据一个宗量对目标方法进行选择,多分派是根据多于一个的宗量对目标方法进行选择。

public class Dispatch {

static class QQ{}
static class _360{}

public static class Father{
public void hardChoice(QQ arg){
System.out.println("father choose qq");
}

public void hardChoice(_360 args){
System.out.println("father choose 360");
}
}

public static class Son extends Father{
public void hardChoice(QQ arg){
System.out.println("son choose qq");
}

public void hardChoice(_360 arg){
System.out.println("son choose 360");
}
}

public static void main(String[] args) {
Father father = new Father();
Father son = new Son();
father.hardChoice(new _360());
son.hardChoice(new QQ());
}
}

结果:

father choose 360
son choose qq


编译期 : 指向 Father.hardChoice(360) 及 Father.hardChoice(QQ)方法    java静态分派属于多分派类型

运行期:实际的接受者,只有这个宗量作为选择依据,java语言的动态分派属于单分派类型

4.虚拟机动态分派的的实现

虚方法表 和 接口方法表  : 方法属于谁就指向谁。

内联缓存

守护内联

8.4 基于栈的字节码解释执行引擎

java 代码执行有两种选择 解释执行和编译执行(通过即时编译器产生本地代码执行)。

8.4.1 解释执行 :

第8章 虚拟机字节码执行引擎

8.4.2 基于栈的指令集与基于寄存器的指令集

基于栈的指令集最主要的优点可移植性,缺点 慢

基于寄存器的指令集: 快,移植差