JVM中方法调用的底层实现,看这一篇就够了
yuyutoo 2024-11-12 14:58 18 浏览 0 评论
方法调用的底层实现
我们编写的Java代码,经过编译后变成class文件,最后经过类加载器加载后进入了JVM的运行时数据区。
但作为程序员真正关心是代码的执行,代码的执行其实本质上是方法的执行,站在JVM 的角度归根到底还是字节码的执行。
main()方法是JVM指令执行的起点,JVM会创建一个main线程来执行main()方法,以触发JVM 一系列指令的执行,真正地把JVM跑起来。接着,在我们的代码中,就是方法调用方法的过程,所以了解方法在JVM 中的调用是非常必要的。
关于方法的调用,JVM中一共提供了5个字节码指令,来调用不同类型的方法:
- invokestatic:用于调用类的静态方法。
- invokespecial:用于调用对象的私有实例方法、构造器this()及父类构造器super()等。
- invokevirtual:用于调用对象的非私有实例方法,比如被修饰符public和protected修饰的方法,大多数方法调用属于这一种、
- invokeinterface:和invokevirtual指令类似,只不过这个指令用于调用接口的方法。
- invokedynamic:用于调用动态方法,常用于lambda表达式。
非虚方法
如果方法在编译期就确定了具体的调用版本,这个版本在运行时是不可变的,在类加载的解析阶段,就会把这些方法的符号引用解析为直接引用(即入口地址),这样的方法称为非虚方法。
只要能被invokestatic和invokespecial指令调用的方法,都可以在解析阶段中确定唯一的调用版本,Java语言里符合这个条件的方法共有静态方法、私有方法、实例构造器、父类构造器4种,再加上被final修饰的方法(尽管它使用 invokevirtual指令调用),这5种方法调用会在类加载的时候就可以把符号引用解析为该方法的直接引用,不需要在方法运行时再去进行动态链接。
invokestatic
invokestatic用来调用类的静态方法。
package com.morris.jvm.methodinvoke;
public class InvokeStaticDemo {
public static void hello() {
}
public static void main(String[] args) {
hello();
}
}
main()方法中会调用静态方法hello(),对应的字节码如下:
0 invokestatic #2 <com/morris/jvm/methodinvoke/InvokeStaticDemo.hello>
3 return
这个方法调用在编译期间就明确以常量池项的形式固化在字节码指令的参数之中了。
invokespecial
invokespecial用于调用对象的私有实例方法、构造器this()及父类构造器super()等。
package com.morris.jvm.methodinvoke;
public class InvokeSpecialDemo {
private void hello() {
}
public static void main(String[] args) {
InvokeSpecialDemo invokeSpecialDemo = new InvokeSpecialDemo();
invokeSpecialDemo.hello();
}
}
main()方法创建了一个InvokeSpecialDemo对象,会调用InvokeSpecialDemo的默认构造方法(使用了invokespecial指令),调用私有方法hello()也使用了使用了invokespecial指令:
0 new #2 <com/morris/jvm/methodinvoke/InvokeSpecialDemo>
3 dup
4 invokespecial #3 <com/morris/jvm/methodinvoke/InvokeSpecialDemo.<init>>
7 astore_1
8 aload_1
9 invokespecial #4 <com/morris/jvm/methodinvoke/InvokeSpecialDemo.hello>
12 return
另外在InvokeSpecialDemo的默认构造方法中会调用父类Objcet的构造方法,也使用了invokespecial指令:
0 aload_0
1 invokespecial #1 <java/lang/Object.<init>>
4 return
虚方法
虚方法与非虚方法相反,在类加载时期,无法确定方法最终的调用版本,如果一个方法被重载了,需要根据传入参数的类型才能确定具体调用哪个方法。
invokevirtual
invokevirtual用于调用对象的非私有实例方法,比如被修饰符public和protected修饰的方法,大多数方法调用属于这一种(排除掉被final修饰的方法)。
package com.morris.jvm.methodinvoke;
public class InvokeVirtualDemo {
public static void main(String[] args) {
InvokeVirtualDemo invokeVirtualDemo = new InvokeVirtualDemo();
invokeVirtualDemo.hello();
}
public void hello() {
}
}
main()方法对应的字节码如下:
0 new #2 <com/morris/jvm/methodinvoke/InvokeVirtualDemo>
3 dup
4 invokespecial #3 <com/morris/jvm/methodinvoke/InvokeVirtualDemo.<init>>
7 astore_1
8 aload_1
9 invokevirtual #4 <com/morris/jvm/methodinvoke/InvokeVirtualDemo.hello>
12 return
invokeinterface
invokeinterface和invokevirtual指令类似,只不过这个指令用于调用接口的方法。
package com.morris.jvm.methodinvoke;
public class InvokeInterfaceDemo {
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
}
};
r.run();
}
}
main()方法对应的字节码如下:
0 new #2 <com/morris/jvm/methodinvoke/InvokeInterfaceDemo$1>
3 dup
4 invokespecial #3 <com/morris/jvm/methodinvoke/InvokeInterfaceDemo$1.<init>>
7 astore_1
8 aload_1
9 invokeinterface #4 <java/lang/Runnable.run> count 1
14 return
lambda表达式
invokedynamic
invokedynamic用于调用动态方法,常用于lambda表达式。
package com.morris.jvm.methodinvoke;
public class InvokeDynamicDemo {
public static void main(String[] args) {
Runnable r = () -> System.out.println("hello");
}
}
main()方法对应的字节码如下:
0 invokedynamic #2 <run, BootstrapMethods #0>
5 astore_1
6 return
使用lambda表达式在编译时会在类中动态生成一个方法,该方法对应的字节码如下:
InnerClasses:
public static final #53= #52 of #56; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
0: #26 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#27 ()V
#28 invokestatic com/morris/jvm/methodinvoke/InvokeDynamicDemo.lambda$main$0:()V
#27 ()V
BootstrapMethods属性在Java1.7以后才有,位于类文件的属性列表中,这个属性用于保存invokedynamic指令引用的引导方法限定符。和上面介绍的四个指令不同,invokedynamic并没有确切的接受对象,取而代之的,是一个叫CallSite的对象。
方法句柄(MethodHandle)
invokedynamic指令的底层,是使用方法句柄(MethodHandle)来实现的。方法句柄是一个能够被执行的引用,它可以指向静态方法和实例方法,以及虚构的get和set方法,从以下案例中可以看到MethodHandle提供的一些方法。
MethodHandle是什么?简单的说就是方法句柄,通过这个句柄可以调用相应的方法。用MethodHandle调用方法的流程为:
- 创建MethodType,获取指定方法的签名(出参和入参)
- 在Lookup中查找MethodType的方法句柄MethodHandle,可以通过相应的findxxx方法得到相应的MethodHandle,相当于MethodHandle的工厂方法。
- 传入方法参数通过MethodHandle调用方法
package com.morris.jvm.methodinvoke;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
public class MethodHandleDemo {
public static void main(String[] args) throws Throwable {
String hello = toString("hello");
System.out.println(hello);
String result = toString(1024);
System.out.println(result);
}
private static String toString(Object o) throws Throwable {
//方法类型表示接受的参数和返回类型(第一个参数是返回参数)
MethodType methodType = MethodType.methodType(String.class);
//方法句柄--工厂方法Factory
MethodHandles.Lookup lookup = MethodHandles.lookup();
//拿到具体的MethodHandle(findVirtual相当于字节码)
MethodHandle methodHandle = lookup.findVirtual(o.getClass(), "toString", methodType);
String obj = (String) methodHandle.invoke(o);
return obj;
}
}
分派
Java是一门面向对象的程序语言,因为Java具备面向对象的3个基本特征:继承、封装和多态。
分派调用过程将会揭示多态性特征的一些最基本的体现,如“重载”和“重写”在Java虚拟机之中是如何实现的?
静态分派
在编译期,根据方法涉及的引用类型(包括参数列表的引用类型和方法的调用者的引用类型)来确定方法调用的(初步)版本,并把相应的符号引用放在字节码指令中,这个步骤叫做静态分派。
先来看一下下面这段代码的执行结果:
package com.morris.jvm.methodinvoke;
public class StaticDispatch{
static abstract class Human{}
static class Man extends Human{ }
static class Woman extends Human{}
public void sayHello(Human guy){
System.out.println("hello,guy!");
}
public void sayHello(Man guy){
System.out.println("hello,gentleman!");
}
public void sayHello(Woman guy){
System.out.println("hello,lady!");
}
public static void main(String[]args){
StaticDispatch sr = new StaticDispatch();
Human man = new Man();
Human woman = new Woman();
sr.sayHello(man);
sr.sayHello(woman);
}
}
运行结果如下:
hello,guy!
hello,guy!
“Human”称为变量的静态类型(Static Type),或者叫做的外观类型(Apparent Type),后面的“Man”则称为变量的实际类型(Actual Type)。
静态类型和实际类型在程序中都可以发生一些变化,区别是静态类型的变化仅仅在使用时发生,变量本身的静态类型不会被改变,并且最终的静态类型是在编译期可知的;而实际类型变化的结果在运行期才可确定,编译器在编译程序的时候并不知道一个对象的实际类型是什么。
代码中定义了两个静态类型相同但实际类型不同的变量,但虚拟机(准确地说是编译器)在重载时是通过参数的静态类型而不是实际类型作为判定依据的。并且静态类型是编译期可知的,因此在编译阶段,Javac编译器会根据参数的静态类型决定使用哪个重载版本,所以选择了sayHello(Human)作为调用目标。所有依赖静态类型来定位方法执行版本的分派动作称为静态分派。
静态分派的典型应用是方法重载。静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机来执行的。
如果将代码改成下面这样,运行结果就不一样了:
Human human=new Man();
sr.sayHello((Man)human);
human=new Woman();
sr.sayHello((Woman)human);
运行结果如下:
hello,gentleman!
hello,lady!
动态分派
在运行时,根据对象的实际类型来确定方法的调用版本,这个步骤叫做动态分派。
package com.morris.jvm.methodinvoke;
public class Dispatch {
static class QQ{}
static class WX{}
public static class Father{
public void hardChoice(QQ arg){
System.out.println("father choose qq");
}
public void hardChoice(WX arg){
System.out.println("father choose weixin");
}
}
public static class Son extends Father{
public void hardChoice(QQ arg){
System.out.println("son choose qq");
}
public void hardChoice(WX arg){
System.out.println("son choose weixin");
}
}
public static void main(String[] args) {
Father father = new Father();
Father son = new Son();
father.hardChoice(new WX());
son.hardChoice(new QQ());
}
}
方法的重写也是使用invokevirtual指令,只是这个时候具备多态性。
invokevirtual指令有多态查找的机制,该指令运行时,解析过程如下:
- 找到操作数栈顶的第一个元素所指向的对象实际类型,记做c。
- 如果在类型c中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法直接引用,查找过程结束,不通过则返回java.lang.IllegalAccessError。
- 否则,按照继承关系从下往上依次对c的各个父类进行第二步的搜索和验证过程。
- 如果始终没找到合适的方法,则抛出java.lang.AbstractMethodError异常,这就是Java语言中方法重写的本质。
静态链接:在类加载过程中的解析阶段将符号引用转化为直接引用。
动态链接:在方法的运行过程中根据方法的参数类型将符号引用转化为直接引用。
动态分派会在JVM运行时会频繁的、反复的去搜索元数据,所以JVM使用了一种优化手段,就是在方法区中建立一个虚方法表,虚方法表示类信息的一种,在类加载过程中的准备阶段完成初始化,存放在方法区,使用虚方法表索引来替代元数据查找以提高性能。
子类中继承但未重写的方法,在子类的虚方法表中存放的入口地址,就是父类的虚方法表中的入口地址,指向父类的实现。在父子类中,相同符号引用的方法(重写)的方法,其在各自虚方法表中的索引相同。
相关推荐
- 对volatile,synchronized,AQS的加锁解锁原理的一些理解
-
一、为什么要加锁,要实现同步多线程编程中,有可能会出现多个线程同时访问同一个共享、可变资源的情况,这个资源我们称之其为临界资源;这种资源可能是:对象、变量、文件等。...
- 注意,不能错过的CAS+volatile实现同步代码块
-
前言:最近看到有人说可以使用CAS+volatile实现同步代码块。心想,确实是可以实现的呀!因为AbstractQueuedSynchronizer(简称AQS)内部就是通过CAS+...
- 面试并发volatile关键字时,我们应该具备哪些谈资?
-
提前发现更多精彩内容,请访问https://dayarch.top/提前发现更多精彩内容,请访问https://dayarch.top/提前发现更多精彩内容,请访问https://dayarch...
- 无锁同步-JAVA之Volatile、Atomic和CAS
-
1、概要本文是无锁同步系列文章的第二篇,主要探讨JAVA中的原子操作,以及如何进行无锁同步。关于JAVA中的原子操作,我们很容易想到的是Volatile变量、java.util.concurrent....
- C/C++面试题(二):std::atomic与volatile
-
volatile是C/C++中的一个关键字,用于告知编译器某个变量的值可能会在程序的控制之外被意外修改(例如被硬件、中断服务程序、多线程环境或其他外部代理)。为了防止编译器对代码进行某些可能破坏...
- VOCs(Volatile Organic Compounds)挥发性有机化合物及测试方法
-
经常看到一些三防漆、涂料、油漆类产品的介绍中提到VOC、VOCs等概念,那么什么是VOC、VOCs和TVOC,VOCs主要包括哪些物质?VOCs的来源有哪些?VOCs的危害及国家标准是什么?一、V...
- 对volatile 及happen—before的理解
-
happen—before规则介绍Java...
- 这一篇我们来了解Synchronized、Volatile、Final关键字
-
题外话:蓝银王觉醒了!!--来自于一个斗罗大陆动漫爱好者(鹅,打钱!)湿兄这两天回家了,办了点大事,回来的时候我弟弟还舍不得我,哭着不愿意让我回京(我弟还是小学),我也心里很不舍,但是还是要回京奋斗...
- 关于 Java 关键字 volatile 的总结
-
1什么是volatilevolatile是Java的一个关键字,它提供了一种轻量级的同步机制。相比于重量级锁synchronized,volatile更为轻量级,因为它不会引起线程上下文...
- 大白话聊聊Java并发面试问题之volatile到底是什么?
-
用最简单的大白话,加上多张图给大家说一下,volatile到底是什么?...
- 为什么要有volatile关键字(volatile 关键字为什么不能保证原子性)
-
在嵌入式编程和多线程编程中,我们常会见到volatile关键字声明的变量。下面说一下volatile关键字的作用:1.保持变量内存可见简而言之就是用volatile声明的变量会告诉编译器和处理器,这个...
- Java的volatile到底怎么理解?(java volatitle)
-
我们都知道,在Java中有很多的关键字,比如synchronize比如volatile,这些都是一些比较关键的,还有final,今天我们就来聊一下这个volatile因为这个vo...
- Java多线程编程中的volatile关键字:解密神秘的共享内存
-
Java多线程编程中的volatile关键字:解密神秘的共享内存在Java多线程编程的世界里,volatile关键字就像一位低调却至关重要的守护者。它默默无闻地站岗放哨,确保多个线程之间能够正确地共享...
- 你了解volatile关键字的作用吗?(关键字volatile有什么含意?并举出三个不同的例子?)
-
【死记硬背】volatile关键字主要用于保持内存的变量可见性和禁止重排序。变量可见性:当一个线程改变了变量的值,那么新的值对于其他线程也是可以立即获取到的。禁止重排序:...
- 谈谈你对volatile 关键字作用和原理的理解
-
一位6年工作经验的小伙伴,在某里二面的时候被问到“volatile”关键字。然后,就没有然后了…同样,还有一位4年的小伙伴,去某团面试也被问到“volatile关键字“。然后,也没有然后了…...
你 发表评论:
欢迎- 一周热门
-
-
前端面试:iframe 的优缺点? iframe有那些缺点
-
带斜线的表头制作好了,如何填充内容?这几种方法你更喜欢哪个?
-
漫学笔记之PHP.ini常用的配置信息
-
其实模版网站在开发工作中很重要,推荐几个参考站给大家
-
推荐7个模板代码和其他游戏源码下载的网址
-
[干货] JAVA - JVM - 2 内存两分 [干货]+java+-+jvm+-+2+内存两分吗
-
正在学习使用python搭建自动化测试框架?这个系统包你可能会用到
-
织梦(Dedecms)建站教程 织梦建站详细步骤
-
【开源分享】2024PHP在线客服系统源码(搭建教程+终身使用)
-
2024PHP在线客服系统源码+完全开源 带详细搭建教程
-
- 最近发表
-
- 对volatile,synchronized,AQS的加锁解锁原理的一些理解
- 注意,不能错过的CAS+volatile实现同步代码块
- 面试并发volatile关键字时,我们应该具备哪些谈资?
- 无锁同步-JAVA之Volatile、Atomic和CAS
- C/C++面试题(二):std::atomic与volatile
- VOCs(Volatile Organic Compounds)挥发性有机化合物及测试方法
- 对volatile 及happen—before的理解
- 这一篇我们来了解Synchronized、Volatile、Final关键字
- 关于 Java 关键字 volatile 的总结
- 大白话聊聊Java并发面试问题之volatile到底是什么?
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)