关键词:
本篇文章从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语言中方法重写的本质。
jvm学习之内存分配和垃圾回收
阅读书籍:Java虚拟机精讲(仅个人阅读后总结)根据受访权限可分为:线程共享内存区和线程私有区线程共享区: 1、java堆区:储存对象实例; 2、方法区:储存运行时常量池、字段和数据、构造函数和普通方法的字节... 查看详情
jvm之内存管理
运行时数据区包括五部分:程序计数器,JVM栈,本地方法栈,堆,方法区,前三种线程私有,后两种内存分配是动态的,所以GC只关注这两部分。程序计数器:线程执行字节码的行号指示器,记录字节码指令地址,执行本地方法... 查看详情
spark学习之路sparkcore的调优之资源调优jvm的基本架构
一、JVM的结构图1.1 Java内存结构JVM内存结构主要有三大块:堆内存、方法区和栈。堆内存是JVM中最大的一块由年轻代和老年代组成,而年轻代内存又被分成三部分,Eden空间、FromSurvivor空间、ToSurvivor空间,默认情况下年轻代按照8:... 查看详情
java中的静态分配——堆、栈和永久代
】java中的静态分配——堆、栈和永久代【英文标题】:staticallocationinjava-heap,stackandpermanentgeneration【发布时间】:2011-04-2011:05:41【问题描述】:我最近阅读了很多关于java中的内存分配方案的内容,并且在我从各种来源阅读时有很... 查看详情
理解jvm之内存分配以及分代思想实现
1.基本内存分批策略 大多数情况在新生代Eden区分配,如果启动了本地线程分配缓冲,将按线程优先在TLAB(线程私有缓冲区)上分配.当Eden区域没有足够的空间时将发起一次MinorGC. 值得注意的是,如果一个对象过大(例如分配了一... 查看详情
spark学习之路(十三)sparkcore的调优之资源调优jvm的基本架构[转](代码片段)
JVM的结构图Java内存结构JVM内存结构主要有三大块:堆内存、方法区和栈。堆内存是JVM中最大的一块由年轻代和老年代组成,而年轻代内存又被分成三部分,Eden空间、FromSurvivor空间、ToSurvivor空间,默认情况下年轻代按照8:1:1的比例... 查看详情
jvm方法调用之静态分派
...章讲静态分派。 1.静态分派所有依赖静态类型来定位方法执行版本的分派动作称为静态分派。静态分派的典型应用是方法重载。静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机来执行的。那么 查看详情
jvm内存分配
...details/80093621.Java技术体系模块图2:Jvm内存区域模型 1.方法区也称”永久代”、“非堆”, 它用于存储虚拟机加载的类信息、常量、静态变量、是各个线程共享的内存区域。默认最小值为16MB,最大值为64MB,可以通过-XX:Per... 查看详情
c#静态方法和动态方法的对比,越详细越好
静态方法是在内存对于类只存在一个,所以调用的时候可以用类.静态方法(),而非静态方法是跟随对象的,每次New了一个对象,就会存在这样一个方法,调用的时候必须得先实例化类,然后用对象调用。参考技术A所谓静态方法就... 查看详情
深入浅出的jvm(代码片段)
...候操作锁分配的内存区。运行时内存区分五个部分:堆、方法区、栈、本地方法栈、程序计数器, -本地方法接口:主要是调用c或者c++实现的本地方法及返回结果。 (2)jvm的内存结构 jvm内存结构主要由三大块... 查看详情
jvm源码分析之堆外内存完全解读
...造函数我们知道,真正的内存分配是使用的Bits.reserveMemory方法通过上面的代码我们知道可以通过-XX:Max 查看详情
c语言学习笔记--动态内存分配
1.动态内存分配的意义(1)C语言中的一切操作都是基于内存的。(2)变量和数组都是内存的别名。 ①内存分配由编译器在编译期间决定 ②定义数组的时候必须指定数组长度 ③数组长度是在编译期就必须确定... 查看详情
软考笔记之存储管理
软考随堂笔记考点1 实存管理考点2 虚存管理程序的装入(重定位)(将逻辑地址转换成物理地址)静态重定位:静态重定位是在虚空间程序执行之前由装配程序完成地址影射工作。动态重定位:动态重定位是在程序执行过程中... 查看详情
c之静态内存和动态内存
静态内存:*自动申请,自动释放*大小固定,内存空间连续*从栈上分配的内存叫静态内存动态内存:*程序员自己申请*new/malloc*大小取决于虚拟内存的大小,内存空间不连续*java中自动回收,C中需要程序员使用free函数手动释放*从... 查看详情
深入理解jvm之jvm内存区域与内存分配
在学习jvm的内存分配的时候,看到的这篇博客,该博客对jvm的内存分配总结的很好,同时也利用jvm的内存模型解释了java程序中有关参数传递的问题。 博客出处: http://www.cnblogs.com/hellocsl/p/3969768.html?utm_source=tuicool&u... 查看详情
jvm之年轻代(新生代)老年代永久代以及gc原理详解gc优化
...现了图中有些分数8/10和1/10,这是默认配置下各个代内存分配比例。举个栗子:假如总heapmax分配1200M,那么年轻代占用1/3就是400M,老年代占2/3就是800M。Eden占年轻代的8/ 查看详情
jvm内存结构
JVM内存结构主要有三大块:堆内存、方法区和栈。堆内存是JVM中最大的一块由年轻代和老年代组成,而年轻代内存又被分成三部分,Eden空间、FromSurvivor空间、ToSurvivor空间,默认情况下年轻代按照8:1:1的比例来分配;方法区存储类... 查看详情
jvm内存结构
JVM内存结构主要有三大块:堆内存、方法区和栈。堆内存是JVM中最大的一块由年轻代和老年代组成,而年轻代内存又被分成三部分,Eden空间、FromSurvivor空间、ToSurvivor空间,默认情况下年轻代按照8:1:1的比例来分配;方法区存储类... 查看详情