Java反射操作百倍性能优化

欢迎来到啾啾的博客🐱。
记录学习点滴。分享工作思考和实用技巧,偶尔也分享一些杂谈💬。
有很多很多不足的地方,欢迎评论交流,感谢您的阅读和评论😄。

目录

  • 引言
  • 避免在性能敏感的热点代码中使用反射
  • 缓存反射对象
  • 使用setAccessible(true)
  • 使用MethodHandle
    • 演示
  • 生成字节码来避免反射

引言

总所周知,反射操作性能开销相对较大。
然而,一些技巧可以显著优化反射操作的性能。

资料引用:Java Reflection, 1000x Faster

避免在性能敏感的热点代码中使用反射

这是最重要的一条原则。因为反射涉及动态类型解析,会阻碍JVM的即时编译(JIT)优化。在需要频繁调用的代码中,应尽量避免使用反射。

缓存反射对象

反射操作中,Class.forName()、Class.getMethod() 和 Class.getField() 这类查找操作非常耗时。如果需要多次对同一个类或方法进行反射操作,应该将查找到的 Class、Method、Field 和 Constructor 对象缓存起来,避免重复查找。

// 不推荐的方式
public void badReflection(Object obj) throws Exception {Method method = obj.getClass().getMethod("doSomething");method.invoke(obj);
}// 推荐的方式:使用Map缓存Method对象
private final Map<String, Method> methodCache = new ConcurrentHashMap<>();public void goodReflection(Object obj) throws Exception {String className = obj.getClass().getName();Method method = methodCache.computeIfAbsent(className, k -> {try {return obj.getClass().getMethod("doSomething");} catch (NoSuchMethodException e) {throw new RuntimeException(e);}});method.invoke(obj);
}

使用setAccessible(true)

当需要调用非公共(private, protected, default)的成员时,必须先调用 setAccessible(true) 来跳过Java语言的访问权限检查。这可以显著提升反射调用的速度。

使用MethodHandle

从Java 7开始引入的 MethodHandle 提供了一种比反射更现代、性能更好的动态方法调用机制。它与反射不同,是“类型化”的,并且可以更好地被JVM优化。
常用于处理处理在编译时未知,在运行时才确定的调用。

和反射区别如下:

特性反射 (java.lang.reflect.Method)方法句柄 (java.lang.invoke.MethodHandle)
本质描述方法元数据的信息类指向方法字节码的直接引用,像函数指针
性能较慢,每次调用都有安全检查和参数解包/打包开销首次查找有开销,后续调用接近原生调用速度,可被JIT优化
类型安全弱类型。invoke(Object, Object…) 接收任意对象,编译时不检查,运行时可能抛出类型转换异常强类型。句柄有明确的 MethodType,调用时如果类型不匹配,编译或运行时会立即失败
安全性每次调用都检查访问权限仅在创建句柄时检查一次访问权限
灵活性API简单直观API更复杂,但提供强大的句柄组合与转换能力
  • MethodHandle:直接指向方法的引用,调用时可以像普通方法一样,并且可以被JIT编译器优化。
  • LambdaMetafactory:结合 MethodHandle 使用,可以创建出实现了特定函数式接口的Lambda实例,其性能几乎等同于直接的方法调用。这种方式在运行时完全避免了反射的开销。

演示

  • 比如在编写框架时,需要根据配置文件来调用某个对象的特定方法。

配置文件(config.json)

{"className": "com.example.UserValidator","methodToCall": "validate"
}

我们的程序它在编译时完全不知道UserValidator这个类,也不知道validate这个方法。
此时可以使用MethodHandle来动态

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;// --- 假设这是用户编写的类 ---
class UserValidator {public boolean validate() {System.out.println("UserValidator is validating...");return true;}
}// --- 这是您编写的框架代码 ---
public class Framework {public void executeFromConfig(Object instance, String methodName) {System.out.println("Framework is about to call method '" + methodName + "' on instance " + instance.getClass().getSimpleName());try {// 1. 获取一个查找上下文MethodHandles.Lookup lookup = MethodHandles.lookup();// 2. 动态确定方法的签名(这里假设是无参,返回boolean)MethodType methodType = MethodType.methodType(boolean.class);// 3. 动态查找方法,得到一个MethodHandle//    这里的 instance.getClass() 和 methodName 都是在运行时才确定的!MethodHandle handle = lookup.findVirtual(instance.getClass(), methodName, methodType);// 4. 调用 handle.invoke() 或 invokeExact()//    这里告诉JVM:请在`instance`这个具体的对象上,执行`handle`所代表的方法。boolean result = (boolean) handle.invoke(instance);System.out.println("Execution result: " + result);} catch (Throwable t) {// 注意:invoke() 和 findVirtual() 都会抛出 Throwablet.printStackTrace();}}public static void main(String[] args) {Framework framework = new Framework();UserValidator userValidatorInstance = new UserValidator();// 框架根据运行时信息(比如配置文件),来调用对象的方法framework.executeFromConfig(userValidatorInstance, "validate");}
}
  • 假设我们有两个不同的类,但我们想用一个统一的框架来处理它们的某个方法
class Greeter {public String sayHello(String name) {return "Hello, " + name;}
}class Calculator {public int add(int a, int b) {return a + b;}
}public class AdvancedFramework {public static void main(String[] args) throws Throwable {MethodHandles.Lookup lookup = MethodHandles.lookup();// --- 场景一:调用 Greeter 的 sayHello 方法 ---Greeter greeter = new Greeter();// 1. 定义方法类型:(String) -> StringMethodType mtHello = MethodType.methodType(String.class, String.class);// 2. 查找句柄MethodHandle mhHello = lookup.findVirtual(Greeter.class, "sayHello", mtHello);// 3. 调用,第一个参数是实例本身,后面是方法的参数String result1 = (String) mhHello.invoke(greeter, "World");System.out.println("Greeter result: " + result1); // 输出: Greeter result: Hello, World// --- 场景二:调用 Calculator 的 add 方法 ---Calculator calculator = new Calculator();// 1. 定义方法类型:(int, int) -> intMethodType mtAdd = MethodType.methodType(int.class, int.class, int.class);// 2. 查找句柄MethodHandle mhAdd = lookup.findVirtual(Calculator.class, "add", mtAdd);// 3. 调用int result2 = (int) mhAdd.invoke(calculator, 10, 20);System.out.println("Calculator result: " + result2); // 输出: Calculator result: 30}
}
  • 适配器模式-绑定参数
    假设你有一个方法对象,但想预先填好它的一个参数,生成一个新的、参数更少的方法对象。这在事件处理等场景中非常有用。
import java.lang.invoke.*;public class BindExample {public void printMessage(String level, String message) {System.out.println("[" + level + "]: " + message);}public static void main(String[] args) throws Throwable {BindExample instance = new BindExample();MethodHandles.Lookup lookup = MethodHandles.lookup();// 原始方法句柄: (BindExample, String, String) -> voidMethodType mt = MethodType.methodType(void.class, String.class, String.class);MethodHandle originalHandle = lookup.findVirtual(BindExample.class, "printMessage", mt);// 我们想创建一个 "INFO" 级别的日志记录器// 使用 bindTo 绑定第一个参数(实例)和第二个参数(level)MethodHandle infoLogger = originalHandle.bindTo(instance).bindTo("INFO");// 新的句柄类型变成了: (String) -> void// 现在调用新的句柄,只需要提供 message 参数infoLogger.invoke("This is an informational message."); // 输出: [INFO]: This is an informational message.// 同样,我们可以创建一个 "ERROR" 级别的日志记录器MethodHandle errorLogger = originalHandle.bindTo(instance).bindTo("ERROR");errorLogger.invoke("A critical error occurred!"); // 输出: [ERROR]: A critical error occurred!}
}

生成字节码来避免反射

对于性能要求极高的场景,例如在框架和库的开发中,可以通过直接生成字节码来避免反射。像 ASM、cglib、Byte Buddy 等库允许在运行时动态创建和修改类。虽然首次生成字节码的开销较大,但一旦生成,其执行速度就和普通的Java代码完全一样,并且可以被JIT充分优化。

字节码增强技术内容有点多,感兴趣的可以看这篇入门。
深入浅出 Byte Buddy:掌握 Java 运行时代码操作的利器

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/diannao/86850.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

STM32 _main 里做了什么

Application startup 在大多数嵌入式系统中&#xff0c;进入 main 函数之前需要执行一段初始化序列来设置好系统环境。下图展示的就是这段初始化序列的默认流程&#xff1a; Figure 1. Default initialization sequence __main is responsible for setting up the memory and…

Java八股文——MySQL「SQL 基础篇」

NOSQL和SQL的区别&#xff1f; 面试官您好&#xff0c;SQL&#xff08;关系型数据库&#xff09;和NoSQL&#xff08;非关系型数据库&#xff09;是当今数据存储领域的两大主流阵营。它们之间不是“谁取代谁”的关系&#xff0c;而是两种完全不同的设计哲学&#xff0c;适用于…

华为OD机考-数字螺旋矩阵(JAVA 2025B卷)

public class RotateMatrix {public static void main(String[] args) {// 顺时针螺旋矩阵printMatrixV1();// 逆时针螺旋矩阵//printMatrixV2();}private static void printMatrixV2() {Scanner scan new Scanner(System.in);while(scan.hasNextLine()){String[] line scan.…

【Java工程师面试全攻略】Day7:分布式系统设计面试精要

一、分布式系统概述 分布式系统已成为现代互联网应用的标配架构&#xff0c;据LinkedIn统计&#xff0c;分布式系统设计能力是高级Java工程师薪资差异的关键因素。今天我们将深入解析分布式系统的核心理论和实践&#xff0c;帮助你掌握面试中的系统设计问题。 二、分布式理论…

Excel处理控件Aspose.Cells教程:在Excel 文件中创建、操作和渲染时间线

您可以使用数据透视表时间轴&#xff0c;而无需调整过滤器来显示日期——这是一种动态过滤器选项&#xff0c;可让您轻松按日期/时间进行过滤&#xff0c;并使用滑块控件放大所需的时间段。Microsoft Excel 允许您通过选择数据透视表&#xff0c;然后单击“插入”>“时间轴”…

Python----神经网络发(神经网络发展历程)

年份网络名称突出点主要成就论文地址1989LeNet首个现代卷积神经网络&#xff08;CNN&#xff09;&#xff0c;引入卷积、池化操作手写数字识别先驱&#xff0c;奠定CNN基础MNIST Demos on Yann LeCuns website2012AlexNet首次大规模使用深度卷积神经网络进行图像识别&#xff1…

mvc与mvp

mvc MVC 架构中&#xff0c;Activity/Fragment&#xff08;作为 View 和 Controller&#xff09;直接持有 Model 或异步任务的引用&#xff0c;当页面销毁时&#xff0c;这些长生命周期对象若未正确释放&#xff0c;会导致 Activity 无法被 GC 回收&#xff0c;形成内存泄漏。…

商业智能中的地图可视化模板:助力数据高效呈现

引言 在数字化浪潮席卷的当下&#xff0c;数据可视化的重要性愈发凸显。企业和组织需要从海量的数据中提取有价值的信息&#xff0c;以便做出明智的决策。而可视化地图组件作为数据可视化的关键部分&#xff0c;能够将数据与地理位置相结合&#xff0c;以直观、美观的方式展示…

Opencv 相机标定相关API及原理介绍

Opencv 相机标定相关API及原理介绍 相机标定是计算机视觉中的基础任务,旨在确定相机的​​内参矩阵​​、​​畸变系数​​以及(可选)​​外参​​(相机相对于世界坐标系的旋转和平移)。OpenCV提供了完整的相机标定工具链,核心函数为cv2.calibrateCamera,其原理基于张正…

深入剖析AI大模型:Prompt 从理论框架到复杂任务的全场景实现

今天我们就Prompt实战&#xff0c;实现一下复杂场景&#xff0c;通过这些实战我们就可以更好的理解大模型工作的原理和机制了。我个人觉得Prompt是AI大模型中非常重要的的环节。首先我们还是温习一下Prompt的框架和基础原则。然后我们就文本生成、问答任务及复杂任务三个方面分…

Fractal Generative Models论文阅读笔记与代码分析

何恺明分型模型这篇文章在二月底上传到arXiv预出版网站到现在已经过了三个月&#xff0c;当时我也听说这篇文章时感觉是大有可为&#xff0c;但是几个月不知道忙啥了&#xff0c;可能错过很多机会&#xff0c;但是亡羊补牢嘛&#xff0c;而且截至目前&#xff0c;该文章应该也还…

IntelliJ IDEA代码提示忽略大小写设置详解

目录 前言一、设置步骤1. 打开设置界面2. 进入代码补全设置3. 配置大小写敏感选项新版本&#xff08;2023及以上&#xff09;旧版本&#xff08;2022及以下&#xff09; 4. 保存并应用设置 二、效果验证示例三、注意事项与常见问题1. **适用范围**2. **版本兼容性**3. **设置未…

Oracle集群OCR磁盘组掉盘问题处理

问题描述 填写问题的基础信息。 系统名称 - IP地址 - 操作系统 HP-UNIX 数据库 Oracle 11.2.0.4 两节点RAC 症状表现 问题的症状表现如下 集群的OCR磁盘组掉了一块盘(/dev/rdisk/disk52): 查询集群仲裁盘发现只有两块&#xff08;原来是有三块&#xff09;&#xff…

在WordPress中彻底关闭生成缩略图的方法

在WordPress中彻底关闭生成缩略图有多种方法&#xff0c;以下是几种常见的方法&#xff1a; 方法一&#xff1a;通过修改主题的functions.php文件 登录WordPress后台&#xff1a;进入WordPress后台管理界面。 编辑主题文件&#xff1a; 在左侧菜单中找到“外观”选项&#…

安全-Linux基线核查项点

Linux基线加固/整改 1.限制超级管理员远程登录 修改远程管理程序ssh的配置文件 vi /etc/ssh/sshd_config PermitRootLogin no 重启sshd服务 systemctl restart sshd 2. 修改默认密码生存周期 一个好的密码时间策略如下&#xff1a; vi /etc/login.defs PASS_MAX_DAY 90 最长…

在微信小程序中使用骨架屏

在微信小程序中使用骨架屏可以优化用户体验&#xff0c;避免页面加载时出现白屏现象。以下是详细的使用方法和注意事项&#xff1a; 使用方法 生成骨架屏代码&#xff1a; 打开微信开发者工具&#xff0c;进入需要添加骨架屏的页面。在模拟器面板右下角点击三个点&#xff0c…

网络的那些事——初级——OSPF(1)

&#x1f48e;什么是OSPF? OSPF&#xff08;Open Shortest Path First&#xff0c;开放最短路径优先&#xff09;是一种基于链路状态的内部网关协议&#xff08;IGP&#xff09;&#xff0c;广泛应用于中大型企业及运营商网络。其核心设计目标是解决早期协议&#xff08;如RI…

前端导出PDF(适配ios Safari浏览器)

目前市面上常用的前端导出PDF库组合一般为&#xff1a; 1. html2canvas js-pdf 2. html2canvaspdf-lib 3. domtoimagepdf-lib 因本人项目中导出pdf需求为导出30页及以上的多页pdf&#xff0c;考虑性能问题&#xff0c;选择了 html2canvaspdf-lib 及domtoimagepdf-lib两种方…

physicsnemo开源程序是开源深度学习框架,用于使用最先进的 Physics-ML 方法构建、训练和微调深度学习模型

​一、软件介绍 文末提供程序和源码下载 NVIDIA PhysicsNeMo 是一个开源深度学习框架&#xff0c;用于使用最先进的 SciML 方法构建、训练、微调和推理物理 AI 模型&#xff0c;以实现 AI4 科学和工程。PhysicsNeMo 提供 python 模块来构建可扩展和优化的训练和推理管道&#…

JDBC接口开发指南

1.简介 JDBC&#xff08;Java Data Base Connectivity,java数据库连接&#xff09;是一种用于执行SQL语句的Java API&#xff0c;可以为多种关系数据库提供统一访问&#xff0c;它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准&#xff0c;据此可以构建更高级的工具…