当前位置: 首页 > article >正文

JVM——方法内联之去虚化

引入

在Java虚拟机的即时编译体系中,方法内联是提升性能的核心手段,但面对虚方法调用(invokevirtual/invokeinterface)时,即时编译器无法直接内联,必须先进行去虚化(Devirtualization)——将动态绑定的虚方法转换为静态可确定的直接调用。这一过程是连接多态抽象与高效执行的关键桥梁,直接决定了虚方法能否被有效内联,进而影响程序性能。

虚方法调用的挑战

虚方法调用的本质是动态绑定:运行时根据对象实际类型确定目标方法。例如:

abstract class BinaryOp {public abstract int apply(int a, int b);
}
class Add extends BinaryOp {public int apply(int a, int b) { return a + b; }
}
BinaryOp op = new Add();
op.apply(2, 1); // 编译时无法确定具体调用Add.apply还是Sub.apply

这种动态性导致即时编译器无法直接内联,必须通过去虚化技术将其转换为直接调用,才能进一步展开方法体。

去虚化的核心目标

唯一目标确定:证明虚方法调用存在唯一目标方法,转换为invokestatic/invokespecial等直接调用指令。

条件适配:若无法确定唯一目标,则生成类型测试代码,将虚调用转换为条件化的直接调用。

基于类型推导的完全去虚化:精准定位动态类型

类型推导的核心逻辑

通过数据流分析,在IR图中确定调用者的动态类型,消除虚方法的多态性。典型场景包括明确的对象创建强制类型转换

代码示例:明确的动态类型

public static int foo() {BinaryOp op = new Add(); // 动态类型为Add,无多态可能return op.apply(2, 1);
}
public static int bar(BinaryOp op) {op = (Add) op; // 强制转换,编译器确保运行时类型为Addreturn op.apply(2, 1);
}

IR图分析:动态类型的精准表达

foo方法内联前IR图

0: Start
2: New Add // 创建Add实例,类型明确
9: Invoke#Add.apply // 直接调用Add.apply,无需动态分派
11: Return
  • 2号节点New Add直接确定op的类型,9号Invoke节点明确指向Add.apply,无需虚方法表查找。

bar方法内联前IR图

0: Start
3: InstanceOf // 强制转换前的类型检查
9: Invoke#Add.apply // 转换后类型确定,调用具体实现
11: Return
  • 3号InstanceOf节点确保类型安全,后续调用与foo方法一致。

内联后的极致优化

foo方法内联及逃逸分析后IR图

0: Start
11: Return 3 // 常量折叠后直接返回2+1的结果
  • 内联后Add.apply的代码被展开,结合常量折叠,条件判断和字段访问被优化为单一返回节点。

bar方法内联后IR图

0: Start
11: Return 3 // 同样完成常量折叠,消除类型转换开销
  • 强制转换的安全性由运行时检查保证,但内联后代码路径与foo方法一致。

失败案例:notInlined方法的局限性

public static int notInlined(BinaryOp op) {if (op instanceof Add) { // 理论上可能推导为Add,但编译器选择放弃return op.apply(2, 1);}return 0;
}

IR图分析

10: Invoke#BinaryOp.apply // 仍为虚方法调用,未被去虚化
  • 原因:类型推导需全局数据流分析,成本较高,编译器优先依赖后续去虚化手段。

编译器策略:局部优化优先

C2和Graal仅在无需额外分析即可确定类型时进行类型推导去虚化(如new对象、强制转换),避免全局分析的高成本。这一策略在保持优化效率的同时,覆盖了大部分明确类型场景。

基于类层次分析的完全去虚化:静态结构的深度挖掘

类层次分析的核心思想

通过分析已加载的类,判断抽象方法是否仅有一个实现。若成立,则注册“唯一实现”假设,将虚调用转换为直接调用。

单实现场景:假设的建立与验证

public static int test(BinaryOp op) {return op.apply(2, 1);
}

编译时状态:若仅加载Add类,编译器假设BinaryOp.apply唯一实现为Add.apply

IR图变化

0: Start
13: Constant 3 // 内联Add.apply后的常量结果
8: Return 3 // 直接返回结果,无需类型检测
  • 动态类型检测被移至假设,IR图省略所有类型相关节点。

假设失效与去优化

类加载冲击:后续加载Sub类,假设失效,触发去优化。

// 运行时加载Sub类后,原编译结果被标记为“not entrant”
System.out.println("JITTest::test made not entrant");

假设注册机制:编译器为每个去虚化结果添加类层次假设(如“BinaryOp仅有Add子类”),类加载器实时验证这些假设。

final修饰符的优化价值

显式不可变final class Add明确禁止继承,编译器无需假设,直接确定调用目标。

Effective Final:即使未标记final,若类层次分析确定无子类,仍可去虚化,但需注册假设。

接口方法的特殊性

无法完全去虚化:接口允许动态实现,Java虚拟机必须保留类型测试(如invokeinterface指令的动态检查),因此C2放弃接口方法的类层次分析去虚化,依赖条件去虚化。

条件去虚化:动态类型的概率性匹配

类型Profile:运行时类型的记忆库

Java虚拟机为每个虚调用点收集高频出现的动态类型(如AddSub),形成类型Profile。默认最多记录2个类型,超过则视为不完整。

条件去虚化的实现过程

伪代码逻辑

public static int test(BinaryOp op) {if (op.getClass() == Add.class) { // 匹配Profile中的类型return 2 + 1; // 内联Add.apply} else if (op.getClass() == Sub.class) {return 2 - 1; // 内联Sub.apply} else {// 处理未记录类型(去优化或虚调用)}
}

IR图关键节点:TypeSwitch的作用

完整Profile场景

27: TypeSwitch // 按Profile中的类型依次匹配
21: Deopt TypeCheckInliningViolated // 匹配失败时触发去优化
  • 若所有记录类型均不匹配,且Profile完整(记录所有出现过的类型),则重新收集类型并去优化。

不完整Profile场景(Graal特有)

21: Invoke#BinaryOp.apply // 回退到虚方法调用
  • Graal生成虚调用代码,通过内联缓存或方法表动态绑定,避免频繁去优化。

编译器差异:C2与Graal的策略分歧

C2处理:不完整Profile时直接使用内联缓存,不进行条件去虚化。

Graal处理:生成包含虚调用的IR图,平衡优化收益与编译成本。

性能权衡

优势:覆盖大部分高频类型,提升热点路径性能。

局限:低频类型仍需动态分派,且Profile容量限制可能导致不完整匹配。

IR图深度解析:去虚化前后的节点变换

完全去虚化的节点简化

阶段foo方法关键节点变化核心优化点
内联前9号Invoke#BinaryOp.apply(虚调用)存在动态分派开销
去虚化后9号Invoke#Add.apply(直接调用)消除虚方法表查找
内联及优化后13号Constant 3(常量折叠)条件分支与字段访问被消除

条件去虚化的节点膨胀

新增节点TypeSwitch(类型匹配)、Phi(返回值聚合)、Deopt(去优化触发)。

控制流变化:单一调用路径变为多分支结构,每个分支对应一个记录类型的内联代码。

失败场景的IR图特征

notInlined方法:保留Invoke#BinaryOp.apply节点,条件判断未被优化,性能与虚调用一致。

接口方法:必须包含InstanceOfTypeTest节点,无法省略动态类型检测。

实践与调试:揭开去虚化的神秘面纱

复现去优化过程

通过以下代码观察类加载导致的去优化日志:

public class JITTest {static abstract class BinaryOp { /* ... */ }static class Add extends BinaryOp { /* ... */ }static class Sub extends BinaryOp { /* ... */ }public static int test(BinaryOp op) { return op.apply(2, 1); }public static void main(String[] args) throws Exception {// 高频调用触发内联for (int i = 0; i < 400_000; i++) test(new Add());// 加载Sub类,触发去优化Class.forName("JITTest$Sub");}
}

启动参数:-XX:+PrintCompilation -XX:CompileCommand='dontinline JITTest.test'

预期输出:JITTest::test made not entrant,表示编译结果因假设失效被回收。

观察类型Profile

通过-XX:+PrintTypeProfile打印类型Profile信息,查看虚调用点的动态类型分布:

[TypeProfile] Method: JITTest.test(BinaryOp)InvokeVirtual #BinaryOp.apply:Types: Add (90%), Sub (10%)
  • 输出解读:Add占90%,Sub占10%,编译器据此生成条件判断分支。

代码优化建议

  1. 标记确定类型:对确定无继承的类/方法添加final,简化编译器假设。
  2. 减少动态分派:通过工厂模式限制子类数量,提升类层次分析成功率。
  3. 监控去优化:通过-XX:+PrintDeoptimization跟踪去优化事件,定位低效路径。

总结

去虚化技术是Java虚拟机在动态性与高效执行之间的精妙平衡,它不仅是即时编译器的核心模块,更是理解多态优化的关键窗口。从类型推导的精准打击到条件匹配的动态适应,每一种去虚化方式都体现了编译优化的工程智慧。掌握这些技术,不仅能写出更易被优化的代码,更能深入理解Java性能优化的底层逻辑,在复杂业务场景中释放程序的最大潜力。

去虚化的三重境界

  1. 类型推导:精准定位明确类型,适用于局部作用域内的确定调用。
  2. 类层次分析:基于静态类结构建立假设,覆盖单实现场景。
  3. 条件匹配:借助运行时Profile,处理高频多态调用。

编译器的平衡艺术

  • 效率与安全:类型推导和类层次分析追求极致优化,但受限于假设和类加载动态性。
  • 通用与特殊:条件去虚化牺牲部分优化深度,换取对复杂多态的普遍支持。

开发者的行动指南

  • 代码设计:利用final、密封类(Sealed Class)减少多态层次,降低去虚化难度。
  • 性能调优:通过虚拟机参数观察去虚化效果,针对热点路径优化类型Profile。

相关文章:

JVM——方法内联之去虚化

引入 在Java虚拟机的即时编译体系中&#xff0c;方法内联是提升性能的核心手段&#xff0c;但面对虚方法调用&#xff08;invokevirtual/invokeinterface&#xff09;时&#xff0c;即时编译器无法直接内联&#xff0c;必须先进行去虚化&#xff08;Devirtualization&#xff…...

Objective-C Block 底层原理深度解析

Objective-C Block 底层原理深度解析 1. Block 是什么&#xff1f; 1.1 Block 的本质 Block 是 Objective-C 中的特殊对象&#xff0c;实现了匿名函数的功能 通过 isa 指针继承自 NSObject&#xff0c;可以响应&#xff08;如 copy、retain、release&#xff09;等内存管理方…...

关于IDE的相关知识之二【插件推荐】

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【14后&#x1f60a;///计算机爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于ide插件推荐的相关内容&#xff01…...

Python+Streamlit实现登录页

PythonStreamlit实现登录页 Streamlit 是一个开源的 Python 库&#xff0c;专为数据科学家和机器学习工程师设计&#xff0c;用于快速构建交互式 Web 应用。 其核心功能与特点包括&#xff1a; 1.快速原型开发 2.交互式数据展示 3.极简开发 4.实时更新 5.内置组件 6.无前端依赖…...

RDD案例数据清洗

在 Spark 中&#xff0c;RDD&#xff08;Resilient Distributed Dataset&#xff09;是分布式数据集的基本抽象。数据清洗是数据预处理中的一个重要步骤&#xff0c;通常包括去除重复数据、过滤无效数据、转换数据格式等操作。以下是一个使用 RDD 进行数据清洗的完整示例。 示…...

按键精灵ios脚本新增元素功能助力辅助工具开发(三)

元素节点功能&#xff08;iOSElement&#xff09;​ 在按键精灵 iOS 新版 APP v2.2.0 中&#xff0c;新增了元素节点功能 iOSElement&#xff0c;该功能包含共 15 个函数。这一功能的出现&#xff0c;为开发者在处理 iOS 应用界面元素时提供了更为精准和高效的方式。通过这些函…...

Axure RP9:列表新增

文章目录 列表新增思路新增按钮操作说明保存新增交互设置列表新增 思路 利用中继器新增行实现列表新增功能 新增按钮操作说明 工具栏中添加新增图标及标签,在图标标签基础上添加热区;对热区添加鼠标单击时交互事件,同步插入如下动作:显示/隐藏动作,设置目标元件为新增窗…...

06 mysql之DML

一、什么是DML DML 用于操作数据库中的数据。主要命令包括&#xff1a; INSERT&#xff1a;添加数据SELECT&#xff1a;查询数据UPDATE&#xff1a;修改数据DELETE&#xff1a;删除数据 二、插入数据&#xff08;INSERT&#xff09; 2.1 插入单条记录 -- 插入学生记录&…...

游戏引擎学习第277天:稀疏实体系统

回顾并为今天定下基调 上次我们结束的时候&#xff0c;基本上已经控制住了跳跃的部分&#xff0c;达到了我想要的效果&#xff0c;现在我们主要是在等待一些新的艺术资源。因此&#xff0c;等新艺术资源到位后&#xff0c;我们可能会重新处理跳跃的部分&#xff0c;因为现在的…...

【最新版】likeshop连锁点餐系统-PHP版+uniapp前端全开源

一.系统介绍 likeshop外卖点餐系统适用于茶饮类的外卖点餐场景&#xff0c;搭建自己的一点点、奈雪、喜茶点餐系统。 系统基于总部多门店的连锁模式&#xff0c;拥有门店独立管理后台&#xff0c;支持总部定价和门店定价LBS定位点餐&#xff0c;可堂食可外卖。无论运营还是二开…...

机器学习之决策树模型:从基础概念到条件类型详解

机器学习之决策树模型&#xff1a;从基础概念到条件类型详解 摘要&#xff1a;本文深入探讨决策树模型的概念、构成以及不同条件类型。首先介绍决策树的基本结构和工作原理&#xff0c;随后详细阐述轴心对齐条件与倾斜条件、二元条件与非二元条件的差异及应用场景&#xff0c;…...

网络编程(一)网络编程入门

本节课学习TCP客户端和服务器端编程架构&#xff0c;其分为分为C/S&#xff08;客户端/服务器模式&#xff09;和B/S&#xff08;浏览器/服务器架构模式&#xff09;两种模式。接下来我们分别了解这两种模式 C/S模式 C/S模式&#xff1a;服务器首先先启动&#xff0c;并根据客…...

黑名单中的随机数-leetcode710

题目描述 给定一个整数 n 和一个 无重复 黑名单整数数组 blacklist 。设计一种算法&#xff0c;从 [0, n - 1] 范围内的任意整数中选取一个 未加入 黑名单 blacklist 的整数。任何在上述范围内且不在黑名单 blacklist 中的整数都应该有 同等的可能性 被返回。 优化你的算法&am…...

纯Java实现反向传播算法:零依赖神经网络实战

在深度学习框架泛滥的今天,理解算法底层实现变得愈发重要。反向传播(Backpropagation)作为神经网络训练的基石算法,其实现往往被各种框架封装。本文将突破常规,仅用Java标准库实现完整BP算法,帮助开发者: 1) 深入理解BP数学原理。2) 掌握面向对象的神经网络实现。3) 构建可…...

海纳思(Hi3798MV300)机顶盒遇到海思摄像头

海纳思机顶盒遇到海思摄像头&#xff0c;正好家里有个海思Hi3516的摄像头模组开发板&#xff0c;结合机顶盒来做个录像。 准备工作 海纳斯机顶盒摄像机模组两根网线、两个电源、路由器一块64G固态硬盘 摄像机模组和机顶盒都接入路由器的LAN口&#xff0c;确保网络正常通信。 …...

MCP项目实例 - client sever交互

1. 项目概述 项目目标 构建一个本地智能舆论分析系统。 利用自然语言处理和多工具协作&#xff0c;实现用户查询意图的自动理解。 进行新闻检索、情绪分析、结构化输出和邮件推送。 系统流程 用户查询&#xff1a;用户输入查询请求。 提取关键词&#xff1a;从用户查询中…...

Axure应用交互设计:表格跟随菜单移动效果(超长表单)

亲爱的小伙伴,在您浏览之前,烦请关注一下,在此深表感谢!本文如有帮助请订阅 Axure产品经理精品视频课已登录CSDN可点击学习https://edu.csdn.net/course/detail/40420 课程主题:表格跟随菜单移动 主要内容:表格交互设计、动态面板嵌套、拖动时事件、移动动作 应用场景…...

7系列 之 I/O标准和终端技术

背景 《ug471_7Series_SelectIO.pdf》介绍了Xilinx 7 系列 SelectIO 的输入/输出特性及逻辑资源的相关内容。 第 1 章《SelectIO Resources》介绍了输出驱动器和输入接收器的电气特性&#xff0c;并通过大量实例解析了各类标准接口的实现。 第 2 章《SelectIO Logic Resource…...

github 上的 CI/CD 的尝试

效果 步骤 新建仓库设置仓库的 page 新建一个 vite 的项目&#xff0c;改一下 vite.config.js 中的 base 工作流 在项目的根目录下新建一个 .github/workflows/ci.yml 文件&#xff0c;然后编辑一下内容 name: Build & Deploy Vue 3 Appon:push:branches: [main]permi…...

Scala和Go差异

Scala和Go&#xff08;又称Golang&#xff09;是两种现代编程语言&#xff0c;各自具有独特的特性和设计哲学。 尽管它们都可以用于构建高性能、可扩展的应用程序&#xff0c;但在许多方面存在显著差异。 Scala和Go的详细比较&#xff0c;涵盖它们的异同点&#xff1a; 1. 语…...

yup 使用 3 - 利用 meta 实现表单字段与表格列的统一结构配置(适配 React Table)

yup 使用 3 - 利用 meta 实现表单字段与表格列的统一结构配置&#xff08;适配 React Table&#xff09; Categories: Tools Last edited time: May 11, 2025 7:45 PM Status: Done Tags: form validation, schema design, yup 本文介绍如何通过 Yup 的 meta() 字段&#xff0…...

类初始化方法

一、类初始化方法 成员初始化列表 class Point {int x, y; public:Point(int a, int b) : x(a), y(b) {} };就地初始化&#xff08;C11&#xff09; 声明时初始化。 class Widget {int size 10; // 类内成员初始化vector<int> data{1,2,3}; };特殊情况&#xff1a;静…...

【OpenCV】imread函数的简单分析

目录 1.imread()1.1 imread()1.2 imread_()1.2.1 查找解码器&#xff08;findDecoder&#xff09;1.2.2 读取数据头&#xff08;JpegDecoder-->readHeader&#xff09;1.2.2.1 初始化错误信息&#xff08;jpeg_std_error&#xff09;1.2.2.2 创建jpeg解压缩对象&#xff08;…...

【Linux实践系列】:进程间通信:万字详解共享内存实现通信

&#x1f525; 本文专栏&#xff1a;Linux Linux实践项目 &#x1f338;作者主页&#xff1a;努力努力再努力wz &#x1f4aa; 今日博客励志语录&#xff1a; 人生就像一场马拉松&#xff0c;重要的不是起点&#xff0c;而是坚持到终点的勇气 ★★★ 本文前置知识&#xff1a; …...

【笔记】BCEWithLogitsLoss

工作原理 BCEWithLogitsLoss 是 PyTorch 中的一个损失函数&#xff0c;用于二分类问题。 它结合了 Sigmoid 激活函数和二元交叉熵&#xff08;Binary Cross Entropy, BCE&#xff09;损失在一个类中。 这不仅简化了代码&#xff0c;而且通过数值稳定性优化提高了模型训练的效…...

Oracle SYSTEM/UNDO表空间损坏的处理思路

Oracle SYSTEM/UNDO表空间损坏是比较棘手的故障&#xff0c;通常会导致数据库异常宕机进而无法打开数据库。数据库的打开故障处理起来相对比较麻烦&#xff0c;读者可以参考本书第5章进一步了解该类故障的处理过程。如果数据库没有备份&#xff0c;通常需要设置官方不推荐的隐含…...

为什么 cout<<“中文你好“ 能正常输出中文

一, 简答: 受python3字符串模型影响得出的下文C字符串模型结论 是错的&#xff01;C的字符串和python2的字符串模型类似&#xff0c;也就是普通的字符串是ASCII字符串和字节串两种语义&#xff0c;类似重载或多态&#xff0c;有时候解释为整数&#xff0c;有时候是字节串。Uni…...

Leetcode 3547. Maximum Sum of Edge Values in a Graph

Leetcode 3547. Maximum Sum of Edge Values in a Graph 1. 解题思路2. 代码实现 题目链接&#xff1a;3547. Maximum Sum of Edge Values in a Graph 1. 解题思路 这一题主要是在问题的分析上面。由题意易知&#xff0c;事实上给定的图必然只可能存在三种可能的结构&#x…...

关于Go语言的开发环境的搭建

1.Go开发环境的搭建 其实对于GO语言的这个开发环境的搭建的过程&#xff0c;类似于java的开发环境搭建&#xff0c;我们都是需要去安装这个开发工具包的&#xff0c;也就是俗称的这个SDK&#xff0c;他是对于我们的程序进行编译的&#xff0c;不然我们写的这个代码也是跑不起来…...

Flutter PIP 插件 ---- 为iOS 重构PipController, Demo界面,更好的体验

接上文 Flutter PIP 插件 ---- 新增PipActivity&#xff0c;Android 11以下支持自动进入PIP Mode 项目地址 PIP&#xff0c; pub.dev也已经同步发布 pip 0.0.3&#xff0c;你的加星和点赞&#xff0c;将是我继续改进最大的动力 在之前的界面设计中&#xff0c;还原动画等体验一…...