虚拟机每次方法的调用和返回都伴随着栈帧的入栈和出栈,而每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用(表明该栈帧执行的是哪个方法),持有这个引用是为了支持方法调用中的动态连接。这些符号引用中一部分会在类加载阶段或者第一次使用的时候转换成直接引用(即在验证、准备、解析的解析阶段,比如说类中的静态方法、私有方法以及final修饰的方法,在编译期就可以明确调用的版本,而不会产生歧义),这种转换称为静态解析;另外一部分将在每一次的运行期间转化为直接引用,这部分称为动态连接。

方法返回包含正常完成出口和异常完成出口。无论哪种方式返回,在方法退出后,要返回到方法被调用的位置,程序才可以继续执行,返回时,栈帧中会保存一些信息,以帮助恢复调用后状态。

正常完成出口是执行引擎遇到任意一个方法返回字节码指令,根据返回指令决定是否需要返回值以及返回值类型。

异常完成出口是方法执行中遇到异常,且异常在方法体内没有得到处理(虚拟机内部异常、athrow字节码指令产生的异常等),这种方式退出时不会给上层调用者任何返回值的。

方法调用,主要是确定被调用方法的版本,即将符号引用转换为直接引用,确定具体函数的入口地址的过程。所有方法在Class文件里存储的都是一个常量池里的符号引用,不是方法在实际运行时的内存布局中的入口地址(相当于前面所说的直接引用)。方法调用有下述四种字节码指令:

a.invokestatic:调用静态方法

b.invokespecial:调用实例构造器<init>方法、私有方法和父类方法

c.invokevirtual:调用所有的虚方法

d.invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象。

解析,解析的前提是方法在程序真正运行之前就有一个可确定的调用版本,而且这个方法的调用版本是运行期不可改变的。换言之,调用目标代码在代码写好、编译器进行编译时就必须确定下来。要解析的方法一般具有“编译期可知,运行期不变”的特征,而满足这个要求的方法主要由静态方法(同类型关联)和私有方法(外部不可访问)两类,他们不能通过继承或别的方式重写出其他的版本。但凡被invokestatic和invokespecial指令调用的方法,都可以在解析阶段确定唯一的调用版本,符合这个条件的有静态方法、私有方法、实例构造器和父类方法四类,他们在类加载的时候就会把符号引用解析为该方法的直接引用。这些方法被称为非虚方法,其它方法被称为虚方法(final方法除外,final方法不可被覆盖,没有其他版本,无需进行多态选择)。

public class Test{
  public static void test(){
   System.out.println("Hello world"); 
  }
public static void main(String[] args){
Test.test();
}
}

上述代码就很容易确定方法调用结果,这个符号引用就是在编译期换为直接引用的。

解析调用是一个静态过程,编译期可完全确定,在类装载的解析阶段就会把涉及的符号引用全部转变为可确定的直接引用,不会延迟到运行期再去完成。

分派(动态分派和静态分派),分派调用可能是静态的也可能是动态的,根据分派依据的宗量数可分为单分派和多分派,组合起来就构成了静态单分派、静态多分派、动态单分派和动态多分派。

public class Test5 {
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(Women guy){
System.out.println("Hello,lady");
}
public void sayHello(Man guy){
System.out.println("Hello,gentleman");
}
public static void main(String[] args) {
Human man = new Man();
Human women = new Women();
Test5 t = new Test5();
t.sayHello(man);
t.sayHello(women);
}
}

  上述代码为方法重载的一个例子,main中类型Human称为变量man的静态类型,Man为实际类型,静态类型的变化仅仅在使用时发生,变量本身的静态类型不会改变,并且最终的静态类型是在编译期可知的;实际类型变化的结果在运行期才可确定,编译器在编译程序的时候并不知道一个对象的实际类型是啥。在main中两次sayHello调用,使用哪个版本完全取决于传入参数的数量和类型。虽然代码中参数的静态类型和实际类型不一致,但是虚拟机(编译期)重载时是通过参数的静态类型而不是实际类型作为判定依据的。且静态类型编译期可知。

  所有依赖静态类型来定位方法执行版本的分派动作都称为静态分派。典型的应用就是方法重载。静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机来执行的。

动态分派方法的重写有紧密联系,见代码:

package com.testone;

public class Test5 {
static abstract class Human{
protected abstract void sayHello();
}
static class Man extends Human{
@Override
protected void sayHello() {
// TODO Auto-generated method stub
System.out.println("man say hello");
}
}
static class Women extends Human{
@Override
protected void sayHello() {
// TODO Auto-generated method stub
System.out.println("women say hello");
}
} public static void main(String[] args) {
Human man = new Man();
Human women = new Women();
man.sayHello();
women.sayHello();
man = new Women();
man.sayHello(); }
}

 sayHello是所要执行的方法,sayHello方法属于实例方法,在调用时虚拟机会将方法所属对象作为参数传入,此时会根据该对象的实际类型C在类型C的定义中查找匹配sayHello的方法,并进行访问权限校验,通过则返回这个方法的直接引用,查找结束;不通过则返回IllegalAccessError异常。否则,按继承关系沿C的父类中进行搜索和验证,如果最终都没有找到合适的方法,则抛出异常。

动态分派中第一步就是在运行期确定接收者的实际类型,因而运行方法时,根据方法声明在各自对象实际类型中进行匹配,从而实现动态的分派,使得结果表现为多态的。

单分派与多分派:方法接收者和方法的参数统称为方法的宗量,单分派为根据一个宗量对目标方法进行选择,多分派是根据多于一个宗量对目标方法进行选择。

package com.testone;

public class Test5 {
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 arg){
System.out.println("father choose 360");
}
}
public static class Son extends Father{ @Override
public void hardChoice(QQ arg) {
// TODO Auto-generated method stub
System.out.println("son choose qq");
} @Override
public void hardChoice(_360 arg) {
// TODO Auto-generated method stub
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());
}
}

  首先看main中第一句加粗的语句,实际类型和静态类型相同,但是内部方法进行了重载,这个采用静态分派,在静态分派的过程中需要根据静态类型和参数类型确定调用的方法,是根据两个宗量进行选择,因而属于多分派,所以java语言的静态分派属于多分派类型

  再看第二句加粗的语句,因为实际类型和静态类型不同,且覆盖了父类方法,这个采用动态分派,在动态分派的过程中,因为方法签名已经确定,所以方法的参数类型及个数已经可以确定(此时参数的静态类型和实际类型都不会对于方法的选择构成影响),但是调用哪个对象的方法是无法确定的,这会影响方法的选择,唯一可以影响虚拟机选择的因素只有此方法的接收者的实际类型,故而只有一个宗量作为选择的依据,所以java语言的动态分派属于单分派类型

Java虚拟机的更多相关文章

  1. 深入Java虚拟机--判断对象存活状态

    程序计数器,虚拟机栈和本地方法栈 首先我们先来看下垃圾回收中不会管理到的内存区域,在Java虚拟机的运行时数据区我们可以看到,程序计数器,虚拟机栈,本地方法栈这三个地方是比较特别的.这个三个部分的特点 ...

  2. 【深入Java虚拟机】之四:类加载机制

    类加载过程     类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载.验证.准备.解析.初始化.使用和卸载七个阶段.它们开始的顺序如下图所示: 其中类加载的过程包括了加载.验 ...

  3. 《深入理解Java虚拟机》类文件结构

    上节学习回顾 在上一节当中,主要以自己的工作环境简单地介绍了一下自身的一些调优或者说是故障处理经验.所谓百变不离其宗,这个宗就是我们解决问题的思路了. 本节学习重点 在前面几章,我们宏观地了解了虚拟机 ...

  4. 《深入理解Java虚拟机》调优案例分析与实战

    上节学习回顾 在上一节当中,主要学习了Sun JDK的一些命令行和可视化性能监控工具的具体使用,但性能分析的重点还是在解决问题的思路上面,没有好的思路,再好的工具也无补于事. 本节学习重点 在书本上本 ...

  5. 《深入理解Java虚拟机》虚拟机性能监控与故障处理工具

    上节学习回顾 从课本章节划分,<垃圾收集器>和<内存分配策略>这两篇随笔同属一章节,主要是从理论+实验的手段来讲解JVM的内存处理机制.好让我们对JVM运行机制有一个良好的概念 ...

  6. JVM学习(1)——通过实例总结Java虚拟机的运行机制

    俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下: JVM的历史 JVM的运行流程简介 JVM的组成(基于 Java 7) JVM调优参数:-Xmx和-Xms ...

  7. Elasticsearch Java 虚拟机配置详解

    Elasticsearch对Java虚拟机进行了预先的配置.通常情况下,因为这些配置的选择还是很谨慎的,所以你不需要太关心,并且你能立刻使用ElasticSearch. 但是,当你监视ElasticS ...

  8. 如何写出让java虚拟机发生内存溢出异常OutOfMemoryError的代码

    程序小白在写代码的过程中,经常会不经意间写出发生内存溢出异常的代码.很多时候这类异常如何产生的都傻傻弄不清楚,如果能故意写出让jvm发生内存溢出的代码,有时候看来也并非一件容易的事.最近通过学习< ...

  9. Java虚拟机(JVM)以及跨平台原理详细的介绍

    相信大家已经了解到Java具有跨平台的特性,可以"一次编译,到处运行",在Windows下编写的程序,无需任何修改就可以在Linux下运行,这是C和C++很难做到的.那么,跨平台是 ...

  10. Java虚拟机浅探

    简介 对于java开发人员了来说,对java虚拟机肯定有着或多或少的了解.因为有了虚拟机的存在,才会使得java的内存管理变得那么方便,不再像C++那样使用new/delete来直接管理内存.知名的j ...

随机推荐

  1. 响应者链条,如何获取最佳的点击view 以及内部实现

    事件的产生与传递 事件是如何产生与传递的? 当发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中. UIApplication会从时间队列中取出最前面的时间,并将事件 ...

  2. css 上下滚动效果

    <html> <head> <style> .scroll{ overflow:hidden; width:100%; } .scrollout{ height:2 ...

  3. 5. UIView

    1. UIView 的初认识 官方文档 UIView class defines a rectangular area on the screen and the interfaces for man ...

  4. Java笔记:关键字

    关键字 描述 abstract 抽象方法,抽象类的修饰符 assert 断言条件是否满足 boolean 布尔数据类型 break 跳出循环或者label代码段 byte 8-bit 有符号数据类型 ...

  5. express中的路由

    一.读取静态文件 基本代码: "use strict"; const express = require("express"); let app = expre ...

  6. 如何生成git的公钥和私钥

    转载地址:http://blog.csdn.net/wqjsir/article/details/17386087 一. Git windows 客服端(MsysGit)下载 下载地址:http:// ...

  7. LeetCode 22. Generate Parentheses

    Given n pairs of parentheses, write a function to generate all combinations of well-formed parenthes ...

  8. Java面试常见知识点总结(三)

    21.volatile关键字: 一旦一个共享变量(类的成员变量.类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:   (1) 保证了不同线程对这个变量进行操作时的可见性,即一个线程 ...

  9. ActiveMQ

    前言 MQ--Message Queue,中文翻译为"消息队列",维基百科上的这样描述: 消息队列(英语:Message queue)是一种进程间通信或同一进程的不同线程间的通信方 ...

  10. 解决ScrollView 嵌套 GridView 单行显示问题

    简单重写GridView package com.hh.beauter.my_ui; import android.content.Context; import android.util.Attri ...