执行引擎是java虚拟机最核心的组成之一。 “虚拟机”是相对于“物理机”的概念,执行引擎在执行java代码的时候可能有解释执行和编译执行(通过即时编译器产生本地代码执行)。
栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构。栈帧包括 局部变量表、操作数栈、动态连接和方法返回地址等信息。
对于执行引擎来说,活动线程中,只有栈顶的栈帧是有效的。称为当前的栈帧(Current Stack Frame),栈帧所有关联的方法称为当前的方法(current Method)。
局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。在编译成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。被清除一次。
操作数栈也常称为操作栈,它是一个先入后出(Last In First Out LIFO])栈。同局部变量表一样。
刚开始操作数栈是空的,在方法的执行过程中,会有各种字节码指令向操作数栈中写入和提取内容。上个局部变量表可以与下一个操作数栈共享区域。
Java虚拟机的解释执行引擎称为“基于栈的执行引擎”,其中所指的“栈”就是操作数栈。
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。
静态解析:类加载阶段或第一次使用的时候转化为直接引用。
动态连接:每一次运行期间转化为直接引用。
正常完成出口 :遇到return
异常完成出口:本方法内没有对异常进行处理。
其他补充信息,例如调试信息。一般会把动态连接、方法返回地址与其他附加信息全部归为一类称为栈帧信息。
方法调用并不等于执行,确定调用那个方法,不涉及具体运行过程。
在编译阶段就是已经确定调用那些方法,这个方法的调用称为解析(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方法)分派可以调用静态或动态,根据宗量数分为单分派和多分派,两两组合静态单分派、静态多分派、动态单分派、动态多分派四种。
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'); } } 类似:如果你最好的选择,你肯定会选择最好,不行其次、再其次、一直到你的底线。父类方法是抽象,子类需要实现。
方法的接收者与方法的参数统称为方法的宗量,单分派是根据一个宗量对目标方法进行选择,多分派是根据多于一个的宗量对目标方法进行选择。
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语言的动态分派属于单分派类型
虚方法表 和 接口方法表 : 方法属于谁就指向谁。
内联缓存
守护内联
java 代码执行有两种选择 解释执行和编译执行(通过即时编译器产生本地代码执行)。
基于栈的指令集最主要的优点可移植性,缺点 慢
基于寄存器的指令集: 快,移植差