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

xiaoxiao2021-02-28  143

8.1. 概述

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

8.2 运行时栈帧结构

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

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

8.2.1 局部变量表

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

局部变量表的容量以变量槽(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进行调用)、invokestatic invokespecial (在解析阶段确定唯一调用版本)

虚方法 : 除了非虚方法

静态解析示例

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.4.2 基于栈的指令集与基于寄存器的指令集

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

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

转载请注明原文地址: https://www.6miu.com/read-20348.html

最新回复(0)