全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2025-07-07_反射太慢了?那是你不会用LambdaMetafactory!

您的位置:首页 >> 新闻 >> 行业资讯

反射太慢了?那是你不会用LambdaMetafactory! 引言在 Java 的世界里,反射一直是开发者们既爱又恨的功能 一方面,它提供了极大的灵活性,另一方面,反射带来的性能开销却让人头疼不已 在之前的文章中,我们介绍使用 Spring 工具类ReflectionUtils通过缓存、避免创建临时对象的方式来优化反射的性能 但工具类在调用方法时依旧会使用反射的invoke,在高频调用的场景性能还是会与直接调用相差一大截 随着Java8的到来,LambdaMetafactory 为我们打开了新的大门,它「不仅能够使用类似反射的灵活性,在高频调用场景下还能与直接调用的性能相差不大」 本文将深入浅出的介绍 LambdaMetafactory,从使用、性能测试、原理、应用等方面阐述它所带来的优势 使用 LambdaMetafactoryLambdaMetafactory 是 Java 8 引入的一个类,位于java.lang.invoke包下 它的主要任务是在运行时创建 lambda 表达式的实现 LambdaMetafactory 通过 MethodHandle 和 CallSite 机制工作,它能够「在运行时通过 ASM 字节码库生成 lambda 内部类来调用目标方法,从而兼得反射的灵活性与直接调用的性能优势」 MethodHandle 是方法句柄,通过它可以灵活调用目标方法;CallSite 用于动态管理存储方法句柄 MethodHandle,它们是实现动态调用的关键 LambdaMetafactory 的核心在于其LambdaMetafactory.metafactory方法 该方法根据目标方法、接口方法等数据来定义目标 lambda 的行为,并返回一个 CallSite 对象 后续通过获取 CallSite 的目标方法句柄进行调用 使用方法通常分为以下几个步骤: 使用 Lookup 查找要调用的目标方法句柄 MethodHandle根据要生成的 lambda 方法名、调用接口、目标方法类型(返回、入参类型)、句柄等信息创建 CallSite根据 CallSite 的 MethodHandle 获取生成的函数接口实现类再调用接口方法?定义的函数式接口 ?@FunctionalInterfaceinterfaceGreeter{ voidgreet(Stringname);} ?使用 LambdaMetafactory ?publicclassLambdaMetafactoryExample{ publicstaticvoidsayHello(String name){ System.out.println("Hello, "+ name); } publicstaticvoidmain(String[] args)throwsThrowable { // 获取 Lookup 对象 MethodHandles.Lookuplookup=MethodHandles.lookup(); // 编写方法类型 返回、入参类型 MethodTypemethodType=MethodType.methodType(void.class, String.class); // 查找目标方法句柄 MethodHandletargetMethod=lookup.findStatic(LambdaMetafactoryExample.class,"sayHello", methodType); // 准备元数据并创建 CallSite CallSitecallSite=LambdaMetafactory.metafactory( lookup, "greet",// 要生成的lambda方法名 MethodType.methodType(Greeter.class),// 调用点签名 methodType,// 目标方法类型(注意这里应该直接是 methodType) targetMethod,// 目标方法句柄 methodType// 目标方法类型 // 获取并调用 MethodHandlefactory=callSite.getTarget(); Greetergreeter=(Greeter) factory.invokeWithArguments(); // 调用接口方法 greeter.greet("World"); }}总的来说,「LambdaMetafactory 就是通过 lambda 生成接口方法,从而实现灵活调用目标方法」 性能对比反射性能上带来的劣势主要有以下几个原因: 「调用时需要安全检查、类型转换,查找到方法、字段后会创建临时对象」「方法、字段的缓存不是强引用,gc 后会被清空」「动态运行,高频访问无法使用 JIT 优化」JVM 通过解释、编译混合运行,对于高频访问的代码会使用 JIT 将字节码转化为机器码缓存在方法区,无需再进行解释执行,在高频访问的场景下反射无法使用该优化 而 LambdaMetafactory 相比反射具有显著的性能优势,主要原因在于: 「减少开销:减少每次调用时的安全检查和类型转换等操作,使用 Lookup 进行查找」「避免重复计算:一旦 lambda 被初始化,后续调用几乎无额外开销」「即时编译:生成的代码可以被 JVM 即时编译器 (JIT) 优化」这些因素使得 LambdaMetafactory 在循环、频繁调用的场景中尤为出色 测试代码如下,也可以直接看后面的结果表格: publicclassLambdaVsReflectionBenchmark{ // 目标方法 publicstaticvoidsayHello(String name){ name ="Hello,"+ name;// System.out.println("Hello, " + name); } // 使用 LambdaMetafactory 创建 lambda 表达式 privatestaticGreetercreateLambda() throws Throwable{ MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodType methodType = MethodType.methodType(void.class, String.class); MethodHandle targetMethod = lookup.findStatic(LambdaVsReflectionBenchmark.class,"sayHello", methodType); CallSite callSite = LambdaMetafactory.metafactory( lookup, "greet", MethodType.methodType(Greeter.class), methodType.changeReturnType(void.class), targetMethod, methodType MethodHandle factory = callSite.getTarget(); return(Greeter) factory.invokeExact(); } // 使用反射获取目标方法 privatestaticMethodgetReflectiveMethod() throws Exception{ returnLambdaVsReflectionBenchmark.class.getMethod("sayHello", String.class); } // 测试调用次数// private static final long ITERATIONS = 1_000L; privatestaticfinallongITERATIONS =1_000_000_000L; /** * 调用次数:1000 * 直接调用耗时: 0.13 ms * LambdaMetafactory 调用耗时: 0.17 ms * 反射调用耗时: 1.74 ms * * 调用次数:1000000000 * 直接调用耗时: 4501.54 ms * LambdaMetafactory 调用耗时: 4640.59 ms * 反射调用耗时: 6142.39 ms * * @param args * @throws Throwable */ publicstaticvoidmain(String[] args) throws Throwable{ System.out.println("调用次数:"+ ITERATIONS); //直接调用 longstart = System.nanoTime(); for(inti =0; i ITERATIONS; i++) { sayHello("World"); } longend = System.nanoTime(); System.out.printf("直接调用耗时: %.2f ms%n", (end - start) /1e6); // 准备 Lambda Greeter lambdaGreeter = createLambda(); // 测试 Lambda 调用 longlambdaStart = System.nanoTime(); for(inti =0; i ITERATIONS; i++) { lambdaGreeter.greet("World"); } longlambdaEnd = System.nanoTime(); System.out.printf("LambdaMetafactory 调用耗时: %.2f ms%n", (lambdaEnd - lambdaStart) /1e6); // 准备反射方法 Method reflectiveMethod = getReflectiveMethod(); // 测试反射调用 longreflectionStart = System.nanoTime(); for(inti =0; i ITERATIONS; i++) { reflectiveMethod.invoke(null,"World"); } longreflectionEnd = System.nanoTime(); System.out.printf("反射调用耗时: %.2f ms%n", (reflectionEnd - reflectionStart) /1e6); } 直接调用LambdaMetafactory反射循环 1000 次0.13 ms0.17 ms1.74 ms循环 1000000000 次4501.54 ms4640.59 ms6142.39 ms根据表格可以看出「在高频调用的场景下,LambdaMetafactory 与直接调用性能几乎相同,而反射性能几乎慢了将近四分之一」 LambdaMetafactory 工作原理我们从核心方法LambdaMetafactory.metafactory生成 CallSite 作为入口进行分析其实现原理 该方法通过一系列的元数据,使用工厂来构建 CallSite publicstaticCallSitemetafactory(MethodHandles.Lookup caller, String invokedName, MethodType invokedType, MethodType samMethodType, MethodHandle implMethod, MethodType instantiatedMethodType) throwsLambdaConversionException { AbstractValidatingLambdaMetafactory mf; //工厂实例 mf =newInnerClassLambdaMetafactory(caller, invokedType, invokedName, samMethodType, implMethod, instantiatedMethodType, false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY); //校验参数 mf.validateMetafactoryArgs(); //生产CallSite returnmf.buildCallSite();}「InnerClassLambdaMetafactory工厂主要为 lambda 调用点创建内部类、以及封装构建 CallSite」 buildCallSite构建 CallSite 时:「先通过spinInnerClass方法创建内部类,再根据元数据在内部类中找到 MethodHandle 封装为 CallSite」 「创建内部类是通过 ASM 字节码库的 ClassWriter,将元数据写入后转换为流,最后通过 UNSAFE 类的defineAnonymousClass生成类」(逻辑代码如下) privateClass spinInnerClass()throwsLambdaConversionException { //元数据写入ClassWriter cw.visit(CLASSFILE_VERSION, ACC_SUPER + ACC_FINAL + ACC_SYNTHETIC, lambdaClassName,null, JAVA_LANG_OBJECT, interfaces); //转为字节流 finalbyte[] classBytes = cw.toByteArray(); //UNSAFE生成 returnUNSAFE.defineAnonymousClass(targetClass, classBytes,null);}总的来说,「LambdaMetafactory 实际上是使用指定的元数据,通过 ASM 字节码库动态生成内部类,通过调用内部类接口方法来间接实现调用目标方法」 这样就即实现反射调用的灵活,又能享受直接调用的性能,只是初次生成类存在一定的开销 (实际上这也是使用 Lambda 语法糖时会隐式帮助我们做的事情) LambdaMetafactory 虽然能够带来性能优势,但也存在一定的劣势,比如:依赖接口、使用更复杂... 根据不同的应用场景可以从反射、反射工具类、LambdaMetafactory 中选择最适合的解决方案 总结在高频使用反射的场景下,常常会有创建临时对象、软引用缓存被 gc 清空、无法使用 JIT 优化等问题而导致性能受到影响的情况 「LambdaMetafactory 带来了更加优雅的动态调用方式」,虽然会有部分生成内部类的开销,但它解决了长期以来困扰开发者的反射性能问题 「LambdaMetafactory 使用元数据通过 ASM 字节码库、Unsafe 类动态生成匿名内部类,再封装为 Methodhandler、CallSite 进行使用」 (同时它也是 Lambda 语法糖的隐式实现,对于开发者透明) 对于不同的应用场景可以选择反射、Spring ReflectionUtils、LambdaMetafactory等多种方案进行解决问题 AI编程资讯AI Coding专区指南:https://aicoding.juejin.cn/aicoding 点击"阅读原文"了解详情~ 阅读原文

上一篇:2024-09-26_突发!OpenAI CTO Mira Murati离职,高层动荡继续 下一篇:2023-10-28_来接单 | 寻找小红书SEO Manager级Freelancer

TAG标签:

18
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价