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]
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
运行期:实际的接受者,只有这个宗量作为选择依据,java语言的动态分派属于单分派类型
4.虚拟机动态分派的的实现
虚方法表 和 接口方法表 : 方法属于谁就指向谁。
内联缓存
守护内联
8.4 基于栈的字节码解释执行引擎
java 代码执行有两种选择 解释执行和编译执行(通过即时编译器产生本地代码执行)。
8.4.1 解释执行 :
8.4.2 基于栈的指令集与基于寄存器的指令集
基于栈的指令集最主要的优点可移植性,缺点 慢
基于寄存器的指令集: 快,移植差