JVM学习笔记(二)--方法调用之静态分配和动态分配

本篇文章从JVM的角度来理解Java学习中经常提到的重载和重写。

方法调用:方法调用不等同于方法执行,在Java虚拟机中,方法调用仅仅是为了确定调用哪个版本的方法。方法调用分为解析调用和分派。解析调用一定是静态的,而分派可以是静态的,也可以是动态的。我们这里只介绍分派中的静态分配和动态分配。

  • 静态分配:所有依赖静态类型来定位方法执行版本的分派动作称为静态分配。

下面看个例子,顺便来猜一下结果(面试中经常遇到):

 1 class Human {
 2
 3 }
 4
 5 class Man extends Human{
 6
 7 }
 8
 9 class Woman extends Human {
10
11 }
12
13 public class overLoadTest {
14
15     public void sayHello(Human guy){
16         System.out.println("Hello guy!");
17     }
18     public void sayHello(Man man){
19         System.out.println("Hello man!");
20     }
21     public void sayHello(Woman woman){
22         System.out.println("Hello woman!");
23     }
24
25
26     public static void main(String[] args){
27         Human man = new Man();
28         Human woman = new Woman();
29
30         overLoadTest sr = new overLoadTest();
31
32         sr.sayHello(man);
33         sr.sayHello(woman);
34
35     }
36 }

运行结果如下:

如果不明白为什么是这样的结果,我们先来了解一下,什么是静态类型和实际类型。

Human man = new Man();
Human woman = new Woman();

这里Human为静态类型,man和woman为实际类型。区别为静态类型的变化仅仅在使用时发生,变量本身的静态类型不会改变,并且最终的静态类型是在编译期间可知的;而实际类型变化的结果在运行期间才可确定,编译器在编译程序时并不知道一个对象的实际类型是什么。

静态分配最典型的应用就是方法重载。我们再来复习一下方法重载的定义:在一个类中有多个方法,名称相同但参数列表不同(参数个数,参数类型,参数顺序)。上面例子就是方法重载。这里的运行结果毫不意外。


sr.sayHello(man);
sr.sayHello(woman);

man和woman的静态类型都是Human。

  • 动态分配 :所有依赖实际类型来定位方法执行版本的分派动作称为静态分配。

动态分配的典型应用为重写。下面为重写的例子

 1 abstract class Human_Test {
 2     public abstract void sayHello();
 3 }
 4
 5 class Man_Test extends Human_Test{
 6      @Override
 7     public void sayHello() {
 8         System.out.println("Hello man!");
 9     }
10
11 }
12
13 class Woman_Test extends Human_Test {
14      @Override
15     public void sayHello() {
16         System.out.println("Hello woman!");
17     }
18
19 }
20
21 public class OverrideTest {
22     public static void main(String[] args){
23         Human_Test man = new Man_Test();
24         Human_Test woman = new Woman_Test();
25
26         man.sayHello();
27         woman.sayHello();
28
29         man = new Woman_Test();
30         man.sayHello();
31     }
32
33
34
35 }

运行结果:

因为动态分配是根据实际类型来确定要调用的方法的,这样的结果也比较符合我们的思维习惯。那么JVM又是怎么根据实际类型来找到目标方法的呢?用Javap来分析一下字节码:

看上图17和21行,可以看到已经找到了Human中的sayHello(),但是invokevirtual指令是多态查找指令。其过程如下:

1). 找到操作数栈顶的第一个元素所指向的对象的实际类型,记作C.

2). 如果在类型C中找到与常量池中描述符和简单名称都相符的方法,则进行访问权限的校验,如果校验不通过,则返回java.lang.IllegaAccessError异常,校验通过则直接返回方法的直接引用,查找过程结束。

3). 否则,按照继承关系从下往上一次对C的各个父类进行第二步骤的搜索和验证过程。

4). 如果始终还是没有找到合适的方法直接引用,则抛出java.lang.AbstractMethodError异常。

由于invokevirtual指令执行的第一步是在运行时确定接收者的实际类型,所以两次中的invokevirtual指令把常量池中的类方法符号引用解析到不同的直接引用上,这个就是java语言中方法重写的本质。

原文地址:https://www.cnblogs.com/yiRain1992/p/8858543.html

时间: 2024-08-29 18:00:45

JVM学习笔记(二)--方法调用之静态分配和动态分配的相关文章

jvm学习笔记二(减少GC开销的建议)

一:触发主GC(Garbage Collector)的条件 JVM进行次GC的频率很高,但因为这种GC占用时间极短,所以对系统产生的影响不大.更值得关注的是主GC的触发条件,因为它对系统影响很明显.总的来说,有两个条件会触发主GC: 1)当应用程序空闲时,即没有应用线程在运行时,GC会被调用.因为GC在优先级最低的线程中进行,所以当应用忙时,GC线程就不会被调用,但以下条件除外. 2)Java堆内存不足时,GC会被调用.当应用线程在运行,并在运行过程中创建新对象,若这时内存空间不足,JVM就会强

java之jvm学习笔记二(类装载器的体系结构)

java的class只在需要的时候才内转载入内存,并由java虚拟机的执行引擎来执行,而执行引擎从总的来说主要的执行方式分为四种, 第一种,一次性解释代码,也就是当字节码转载到内存后,每次需要都会重新的解析一次, 第二种,即时解析,也就是转载到内存的字节码会被解析成本地机器码,并缓存起来以提高重用性,但是比较耗内存, 第三种,自适应优化解析,即将java将使用最贫乏的代码编译成本地机器码,而使用不贫乏的则保持字节码不变,一个自适应的优化器可以使得java虚拟机在80%-90%的时间里执行优化过的

java之jvm学习笔记六-十二(实践写自己的安全管理器)(jar包的代码认证和签名) (实践对jar包的代码签名) (策略文件)(策略和保护域) (访问控制器) (访问控制器的栈校验机制) (jvm基本结构)

java之jvm学习笔记六(实践写自己的安全管理器) 安全管理器SecurityManager里设计的内容实在是非常的庞大,它的核心方法就是checkPerssiom这个方法里又调用 AccessController的checkPerssiom方法,访问控制器AccessController的栈检查机制又遍历整个 PerssiomCollection来判断具体拥有什么权限一旦发现栈中一个权限不允许的时候抛出异常否则简单的返回,这个过程实际上比我的描述要复杂 得多,这里我只是简单的一句带过,因为这

JVM 学习笔记(二)

JVM 堆中几乎存放着java中所有的对象实例,在在垃圾回收前先要判断对象是否已死,这里对对象的判断主要有: 1.  引用计数法 给对象中添加一个引用计数器,每当有一个地方引用他时,计数器就加1:当引用失效时,计数器就减1.任何时间计数器为0时,对象就不可能在次使用. 2.  根搜索法 通过一系列的名为GC Roots 作为起点,从这些节点开始向下搜索,搜索走过的路径成为引用链(Reference Chain), 当一个对象到GC Roots 没有任何引用链时,则说明此对象时不可到达的. 可以作

angular学习笔记(二十八-附1)-$resource中的资源的方法

通过$resource获取到的资源,或者是通过$resource实例化的资源,资源本身就拥有了一些方法,比如$save,可以直接调用来保存该资源: 比如有一个$resource创建的服务: var service = angular.module('myRecipe.service',['ngResource']); service.factory('Recipe',['$resource',function($resource){ return $resource('/recipe/:id',

JVM 学习笔记

JVM  ----Java  Virtual Machine   (熟称:JAVA虚拟机),JVM 在执行JAVA程序的过程中将内容划分为若干个区域,其有各自的用途和管理机制.如下图: 1.  程序计算器(Program Counter Register)  --  是当前线程所执行字节码的行号指示器,通过改变其值来实现执行不同的代码指令.内存占用小,线程私有,支持多线程处理(多线程时,每个线程有一个独立程序计算器,已达到各自互不影响), 也正是由于这些特点,该区域是JVM规范中唯一没有规定任何

java之jvm学习笔记十三(jvm基本结构)

java之jvm学习笔记十三(jvm基本结构) 这一节,主要来学习jvm的基本结构,也就是概述.说是概述,内容很多,而且概念量也很大,不过关于概念方面,你不用担心,我完全有信心,让概念在你的脑子里变成图形,所以只要你有耐心,仔细,认真,并发挥你的想象力,这一章之后你会充满自信.当然,不是说看完本章,就对jvm了解了,jvm要学习的知识实在是非常的多.在你看完本节之后,后续我们还会来学jvm的细节,但是如果你在学习完本节的前提下去学习,再学习其他jvm的细节会事半功倍. 为了让你每一个知识点都有迹

JVM学习笔记:虚拟机的类加载机制

JVM类加载机制分两部分来总结: (1)类加载过程 (2)类加载器 一.JVM类加载过程 类的加载过程:加载 →连接(验证 → 准备 → 解析)→ 初始化. 类的生命周期:加载 →连接(验证 → 准备 → 解析)→ 初始化 → 使用 →  卸载. 1 加载 1.1 加载阶段要做的3件事情 通过一个类的全限定名来获取该类对应的二进制字节流. 将这个字节流所代表的静态存储结构转化为方法区的数据结构. 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口

java之jvm学习笔记五(实践写自己的类装载器)

java之jvm学习笔记五(实践写自己的类装载器) 课程源码:http://download.csdn.net/detail/yfqnihao/4866501 前面第三和第四节我们一直在强调一句话,类装载器和安全管理器是可以被动态扩展的,或者说,他们是可以由用户自己定制的,今天我们就是动手试试,怎么做这部分的实践,当然,在阅读本篇之前,至少要阅读过笔记三. 下面我们先来动态扩展一个类装载器,当然这只是一个比较小的demo,旨在让大家有个比较形象的概念. 第一步,首先定义自己的类装载器,从Clas