【JVM】虚拟机字节码执行引擎
概念模型上,典型的帧栈结构如下(栈是线程私有的,也就是每个线程都会有自己的栈)。
典型的帧栈结构
//--------------------------测试1---------------------------// public static void main(String[] args){ byte[] placeholder = new byte[64*1000*1000]; System.gc(); } //查看日志,并未回收 [GC (System.gc()) 69437K->63438K(251392K),0.0012879 secs] [Full GC (System.gc()) 63438K->63277K(251392K),0.0058505 secs] //------------------------测试2-----------------------------// public static void main(String[] args) { { byte[] placeholder = new byte[64 * 1000 * 1000]; } System.gc(); } //查看日志,并未回收 [GC (System.gc()) 69437K->63420K(251392K),0.0011785 secs] [Full GC (System.gc()) 63420K->63277K(251392K),0.0058676 secs] //------------------------测试3-----------------------------// public static void main(String[] args) { { byte[] placeholder = new byte[64 * 1000 * 1000]; } int a = 0; System.gc(); } //查看日志,回收了 [GC (System.gc()) 69437K->63454K(251392K),0.0011921 secs] [Full GC (System.gc()) 63454K->777K(251392K),0.0056915 secs]
测试1中在
方法调用方法调用并不等同于方法执行,方法调用阶段的唯一目的就是确定被调用的方法的版本(即调用哪个方法)。一切方法调用在Class文件里存储的都是符号引用,而不是方法的直接引用(方法在实际运行时内存布局中的入口地址)。 在虚拟机中,有5条方法调用字节码指令: 方法的调用可以分为解析调用和分派调用。
Human human = new Man();//这里假设Man是Human的子类
上述代码中: public class StaticDispatch { static abstract class Human{} static class Man extends Human{} static class Woman extends Human{} public void sayHello(Human human){ System.out.println("hello,human"); } public void sayHello(Man man){ System.out.println("hello,man"); } public void sayHello(Woman woman){ System.out.println("hello,woman"); } @Test public void test(){ Human man = new Man(); Human woman = new Woman(); StaticDispatch dispatch = new StaticDispatch(); dispatch.sayHello(man); dispatch.sayHello(woman); } } //最终的打印结果如下:(重载时是以静态类型判断的) hello,human hello,human
编译器虽然可以确定方法的重载版本,但在很多情况下这个重载版本并不是“唯一的”,往往只能确定一个“更加合适的”版本。这种情况的产生的主要原因是字面量不需要定义,所以字面量没有显示的静态类型,它的静态类型只能通过语言上的规则去理解和推测。 public class OverLoad { public static void sayHello(Object object){ System.out.println("hello Object"); } public static void sayHello(int a){ System.out.println("hello int"); } public static void sayHello(long a){ System.out.println("hello long"); } public static void sayHello(Character character){ System.out.println("hello Character"); } public static void sayHello(char c){ System.out.println("hello char"); } public static void sayHello(char... c){ System.out.println("hello char..."); } public static void sayHello(Serializable serializable){ System.out.println("hello Serializable"); } @Test public void test(){ sayHello(‘a‘); } }
以上代码,将打印出
public class DynamicDispatch { static abstract class Human { abstract void sayHello(); } static class Man extends Human { @Override void sayHello() { System.out.println("man say hello"); } } static class Woman extends Human { @Override void sayHello() { System.out.println("woman say hello"); } } @Test public void test() { Human man = new Man(); Human woman = new Woman(); man.sayHello(); woman.sayHello(); } } //将会打印 man say hello woman say hello
通过以上可知,静态分派与动态分派是不同情况下方法调用所采取的不同的分派方式,两者并不是非此即彼的,还可能出现一个方法调用在确定直接引用时,既用到静态分派,又用到动态分派。确定重载方法的时候用到的是静态分派,确定重写方法的时候用到的是动态分派。即重载看参数静态类型,重写看参数实际类型。这里的参数,重载时是指方法的参数列表中那个参数,重写时是指该方法的调用者。
基于栈的字节码解释执行引擎//TODO主要探讨虚拟机如何执行方法中的字节码指令。许多Java虚拟机的执行引擎在执行Java代码的时候都有解释执行(通过解释器执行)和编译执行(通过即时编译器生成本地代码执行)两种选择,这里进讨论解释执行。 解释执行基于栈的指令集和基于寄存器的指令集基于栈的解释器执行过程(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |