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

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

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值