论坛
潜水/灌水快乐,沉淀知识,认识更多同行。
ToB圈子
加入IT圈,遇到更多同好之人。
朋友圈
看朋友圈动态,了解ToB世界。
ToB门户
了解全球最新的ToB事件
博客
Blog
排行榜
Ranklist
文库
业界最专业的IT文库,上传资料也可以赚钱
下载
分享
Share
导读
Guide
相册
Album
记录
Doing
搜索
本版
文章
帖子
ToB圈子
用户
免费入驻
产品入驻
解决方案入驻
公司入驻
案例入驻
登录
·
注册
只需一步,快速开始
账号登录
立即注册
找回密码
用户名
Email
自动登录
找回密码
密码
登录
立即注册
首页
找靠谱产品
找解决方案
找靠谱公司
找案例
找对的人
专家智库
悬赏任务
圈子
SAAS
ToB企服应用市场:ToB评测及商务社交产业平台
»
论坛
›
软件与程序人生
›
后端开发
›
.Net
›
从原理聊JVM(四):JVM中的方法调用原理 ...
从原理聊JVM(四):JVM中的方法调用原理
河曲智叟
金牌会员
|
2023-8-7 20:25:55
|
显示全部楼层
|
阅读模式
楼主
主题
887
|
帖子
887
|
积分
2661
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中符合解析标准的主要是
静态方法
和
私有方法
。前者与类型直接相关,后者对外不可见。
方法调用指令中,invokestatic和invokespecial指令调用的方法,再加上final修饰的方法,都被称作“非虚方法”,他们都可以在解析阶段确定唯一的调用版本。其他的方法都被称作“虚方法”。
3.3 分派
在编译阶段,依赖静态类型确定方法的调用版本,这就叫做
“静态分派”
。
而在运行期,根据实际类型确定方法调用版本被称作
“动态分派”
。
3.3.1 静态分派
直接上个
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
本帖子中包含更多资源
您需要
登录
才可以下载或查看,没有账号?
立即注册
x
回复
使用道具
举报
0 个回复
倒序浏览
返回列表
快速回复
高级模式
B
Color
Image
Link
Quote
Code
Smilies
您需要登录后才可以回帖
登录
or
立即注册
本版积分规则
发表回复
回帖并转播
回帖后跳转到最后一页
发新帖
回复
河曲智叟
金牌会员
这个人很懒什么都没写!
楼主热帖
黑客常见攻击流程
为什么你的局域网需要https加密?怎么 ...
WebGL 及其在 WebRTC 中的应用
Flutter项目打包生成APK
为什么一定要从DevOps走向BizDevOps? ...
综述计算机中关于数符的表示方法 ...
大数据 - ClickHouse
【计算机网络】基础知识点
dotnet 使用 Crossgen2 对 DLL 进行 Re ...
【Java并发入门】03 互斥锁(上):解 ...
标签云
存储
挺好的
服务器
浏览过的版块
Java
快速回复
返回顶部
返回列表