首页 > 其他分享 >从原理聊JVM(四):JVM中的方法调用原理

从原理聊JVM(四):JVM中的方法调用原理

时间:2023-08-07 11:56:52浏览次数:32  
标签:调用 void sayHello static JVM 原理 方法 public

1 引言

多态是Java语言极为重要的一个特性,可以说是Java语言动态性的根本,那么线程执行一个方法时到底在内存中经历了什么,JVM又是如何确定方法执行版本的呢?

2 栈帧

JVM中由栈帧存储方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。每一个方法的调用就是从入栈到出栈到过程。

2.1 局部变量表

局部变量表由变量槽组成,《Java虚拟机规范》指出:“每个变量槽都应该能存放一个boolean、byte、char、short、int、float、reference或returnAddress类型的数据”。

这八种数据类型都可以使用32位或更小的物理内存来存储,如果是64位虚拟机环境下,虚拟机需要通过对齐填充来使变量槽与在32位虚拟机环境下外观一致。

如果是64位的数据类型,比如long和double,JVM会以高位对齐的方式为其分配两个连续的变量槽空间。且规定不允许以任何方式访问这两个变量槽的其中一个,类加载的校验阶段会针对违反规定的行为抛出异常。

类变量会有两次赋值,一次是准备阶段给赋值一个默认值,二是初始化阶段,赋予程序定义的值。但方法变量没有准备阶段,所以没赋值的方法变量不能被使用。

2.2 变量槽的复用

为了节省内存空间,变量槽是可以复用的。当程序计数器的值超过方法体中定义的变量的作用域时,这个变量的变量槽就可以被其他变量复用了。不过虽然这样可以节省内存空间,但对GC有一定影响。

举个例子,如果没有发生即时编译的前提下,在方法清单1中placeholder不会被回收。原因是,方法清单1中gc发生时,变量槽仍然保持着对placeholder的引用,所以不会被标记为可回收对象。而在方法清单2中国呢增加了int a = 0后,placeholder原有的变量槽被变量a复用了,也就不存在引用placeholder的变量槽了,所以placeholder就可以被回收了。

方法清单1:

public static void main (String[] args) {
  {    
    byte[] placeholder = new byte[64 * 1024 * 1024];
  }  
  System.gc();
}

方法清单2:

public static void main (String[] args) {
  {
    byte[] placeholder = new byte[64 * 1024 * 1024];
  }  
  int a = 0;  
  System.gc();
}

但是实际上,大部分程序都是运行在即时编译下的,所以编译器会对其进行优化,实际情况下方法清单1中placeholder也能被回收。

2.3 操作数栈

操作数栈主要作用有二:

1.作为计算过程中的所需变量的临时存储空间

2.存储系统运行过程中的计算中间结果

操作数栈不能通过指针访问,只能通过弹栈和压栈来操作其内部元素。当执行某项指令前会将所需变量压入栈顶,然后真正执行指令时从栈顶依次取出用来执行具体指令,执行完成后会将结果在压入操作数栈。

大多数虚拟机实现会有一些优化处理,将两个栈帧部分重叠:上一个栈帧的部分操作数栈和下一个栈帧的部分局部变量表。不仅节约空间,还让下面栈的操作可以直接使用上面栈的内容,减少了参数传递。

2.4 动态链接

Java文件被编译成Class文件后,变量和方法的引用都作为符号引用保存在Class文件中的常量池中。而对于方法的引用,某些可以在编译期就确定下来称为“直接引用”,而有些方法只能在运行期才能确定下来(比如方法的重载)。

动态链接的作用就是在运行期将符号引用转换为直接引用。

2.5 方法返回地址

一个方法执行完成后,有两种方式退出:正常完成和抛出异常。

当方法A中调用方法B时,A的栈帧中会保存程序计数器的值作为返回地址。而异常退出时,返回地址是要通过异常处理器表来确定的。

方法返回后还会进行几个操作:

1.恢复主调线程对应栈帧中的局部变量表和操作数栈

2.把返回值压入主调线程的栈帧中

3.调整程序计数器到方法调用指令的下一条指令

2.6 附加信息

不同虚拟机在实现时可以自定义一些例如调试、性能收集等信息放到栈帧之中。

3 方法调用

一切方法调用在Class文件里面存储的都只是符号引用,某些调用需要在类加载时甚至运行期间才能确定目标方法的直接引用,这是Java强大的动态扩展能力的基础。

3.1 方法调用指令

JVM共支持以下5种方法调用字节码指令:

•invokestatic调用静态方法

•invokespecial调用构造器()方法、私有方法和父类中的方法

•invokevirtual调用所有虚方法

•invokeinterface调用接口方法,运行期会确定具体实现该接口的对象

•invokedynamic调用运行期动态解析出具体调用的方法

其中,invokestatic和invokespecial指令调用的方法,都可以类加载的解析阶段确定调用的方法版本,Java中符合这个条件的方法共有五种:静态方法、私有方法、实例构造器、父类方法和final修饰的方法(它使用invokevirtual指令调用)。

这五种方法称为“非虚方法”(Non-Virtual Method),剩下的均为“虚方法”(Virtual Method)。

3.2 解析

如果一个方法在类加载的解析阶段就能确定方法的调用版本,那么这类方法的调用被称为解析(Resolution)。

Java中符合解析标准的主要是静态方法私有方法。前者与类型直接相关,后者对外不可见。

方法调用指令中,invokestaticinvokespecial指令调用的方法,再加上final修饰的方法,都被称作“非虚方法”,他们都可以在解析阶段确定唯一的调用版本。其他的方法都被称作“虚方法”。

3.3 分派

在编译阶段,依赖静态类型确定方法的调用版本,这就叫做“静态分派”

而在运行期,根据实际类型确定方法调用版本被称作“动态分派”

3.3.1 静态分派

直接上个

标签:调用,void,sayHello,static,JVM,原理,方法,public
From: https://www.cnblogs.com/Jcloud/p/17611050.html

相关文章

  • qt截图软件中画箭头代码原理
    截图工具中,需要画一个指向箭头, 该箭头的形状解析示意图如下所示,对应的qt代码如下: //画出一个箭头线,主要是算出这几个点。//这个箭头形状是这样的,胖嘟嘟的那种,但是出发点是一个细的QLineFlineOrigin(mPosStart,mPosEnd);lineOrigin.setLength(lineOrigin.length()-arrowHeig......
  • Java:Java程序通过执行系统命令调用Python脚本
    本文实现功能:Java程序调用Python脚本Python脚本importsysdefadd(x,y):returnx+yif__name__=="__main__":print(add(int(sys.argv[1]),int(sys.argv[2])))直接执行$pythonmath.py123Java程序调用Python脚本packageio.github.mouday.utils;importja......
  • Android View绘制原理 - RenderThread
    前面的文章介绍了HardwareRendere在初始化的时候,涉及到了一个组件RenderThread并简要的分析了一下,这篇文章将继续深入的分析一下这个RenderThread,介绍一下它的几个重要特性和功能1ThreadRenderThread首先是继承自ThreadBase,是一个真实的线程。frameworks/base/libs/hwui/rendert......
  • 拆解爬虫使用隧道HTTP代理的原理
    今天,让我们来一起探索一下爬虫如何利用隧道HTTP代理来实现无限可能!本文将为你详解这一原理,并分享一些实用的操作技巧。快来和我一起探索吧!一、隧道HTTP代理是什么?在爬虫的世界里,隧道HTTP代理就像是一个隐身斗篷,可以帮助我们在互联网上隐藏身份。它实际上是位于我们和目标网站之间的......
  • Mitsubishi 三菱FXPLC学习之子程序调用与循环
    上次,我们学习了程序流程转移中的条件跳转CJ,这次,我们接着向子程序调用CALL和FOR循环发起进攻吧!显然,子程序调用CALL和FOR循环和条件跳转CJ一样,都是PLC程序中用于流程转移的,所以,上次所学的程序区、主程序结束指令FEND等知识点可不要丢哟~在这里我也不再赘述了,这是为了给读者......
  • Nodejs 第四章(Npm install 原理)
    在执行npminstall的时候发生了什么?首先安装的依赖都会存放在根目录的node_modules,默认采用扁平化的方式安装,并且排序规则.bin第一个然后@系列,再然后按照首字母排序abcd等,并且使用的算法是广度优先遍历,在遍历依赖树时,npm会首先处理项目根目录下的依赖,然后逐层处理每个依赖包的依......
  • Nodejs 第五章(Npm run 原理)
    npmrunxxx发生了什么按照下面的例子npmrundev举例过程中发生了什么读取packagejson的scripts对应的脚本命令(dev:vite),vite是个可执行脚本,他的查找规则是:先从当前项目的node_modules/.bin去查找可执行命令vite如果没找到就去全局的node_modules去找可执行命令vite如果还......
  • 容斥原理
    Part1:知识点Part2:例题【模板题】区间整除数题意给出一个数组\(a[1..n]\),问在区间\([L,R]\)中有多少个数,至少能被a中的一个数整除。解题思路总体来说,我们可先求出区间\([1,L-1]\)中能被a数组整除的数,再求出\([1,R]\)中能被a数组整除的数,两者相减即是答案那么对于......
  • C与C++之间的相互调用及函数区别
    最近项目需要使用googletest(以下简称为gtest)作为单元测试框架,但是项目本身过于庞大,main函数无从找起,需要将gtest框架编译成静态库使用。因为项目本身是通过纯c语言编写,而gtest则是一个c++编写的测试框架,其中必然涉及c与c++之间的相互调用。注意,本文的前提是,c代码采用gcc等c语言编......
  • 如何将 dubbo filter 拦截器原理运用到日志拦截器中?
    业务背景我们希望可以在使用日志拦截器时,定义属于自己的拦截器方法。实现的方式有很多种,我们分别来看一下。拓展阅读java注解结合springaop实现自动输出日志java注解结合springaop实现日志traceId唯一标识java注解结合springaop自动输出日志新增拦截器与过滤器......