在编译时不知道Java类的最快方法是什么? Java框架通常会这样做。 很多。 它可以直接影响其性能。 因此,让我们对不同的方法进行基准测试,例如反射,方法句柄和代码生成。

用例

假设我们有一个简单的Person类,其中包含名称和地址:

public class Person {...public String getName() {...}public Address getAddress() {...}}

并且我们想使用诸如以下的框架:

  • XStream ,JAXB或Jackson来将实例序列化为XML或JSON。
  • JPA /休眠将人员存储在数据库中。
  • OptaPlanner分配地址(如果他们是游客或无家可归的人)。

这些框架都不了解Person类。 因此,他们不能简单地调用person.getName()

// Framework codepublic Object executeGetter(Object object) {// Compilation error: class Person is unknown to the frameworkreturn ((Person) object).getName();}

相反,代码使用反射,方法句柄或代码生成。

但是这样的代码被称为很多

  • 如果在数据库中插入1000个不同的人,则JPA / Hibernate可能会调用2000次这样的代码:

    • 1000次调用Person.getName()
  • 同样,如果您用XML或JSON编写1000个不同的人,则XStream,JAXB或Jackson可能会进行2000次调用。

显然,当这种代码每秒被调用x次时, 其性能很重要

基准测试

使用JMH,我在带有32GB RAM的64位8核Intel i7-4790台式机上的Linux上使用OpenJDK 1.8.0_111运行了一组微型基准测试。 JMH基准测试有3个分支,5个1秒的预热迭代和1秒的20个测量迭代。

该基准的源代码位于此GitHub存储库中 。

TL; DR结果

  • Java反射很慢。 (*)
  • Java MethodHandles也很慢。 (*)
  • javax.tools生成的代码很快。 (*)

(*)在用例中,我以使用的工作量作为基准。 你的旅费可能会改变。

因此,魔鬼在细节中。 让我们浏览一下实现,以确认我应用了典型的魔术技巧(例如setAccessible(true) )。

实作

直接访问(基准)

我使用了一个普通的person.getName()调用作为基准:

public final class MyAccessor {public Object executeGetter(Object object) {return ((Person) object).getName();}}

每次操作大约需要2.7纳秒:

Benchmark           Mode  Cnt  Score   Error  Units
===================================================
DirectAccess        avgt   60  2.667 ± 0.028  ns/op

直接访问自然是运行时最快的方法,而没有引导成本。 但是它在编译时导入Person ,因此每个框架都无法使用它。

反射

框架在运行时读取getter的明显方法是不预先知道它的方法是通过Java Reflection:

public final class MyAccessor {private final Method getterMethod;public MyAccessor() {getterMethod = Person.class.getMethod("getName");// Skip Java language access checking during executeGetter()getterMethod.setAccessible(true);}public Object executeGetter(Object bean) {return getterMethod.invoke(bean);}}

添加setAccessible(true)调用可使这些反射调用更快,但是即使这样,每个调用也要花费5.5纳秒。

Benchmark           Mode  Cnt  Score   Error  Units
===================================================
DirectAccess        avgt   60  2.667 ± 0.028  ns/op
Reflection          avgt   60  5.511 ± 0.081  ns/op

反射比直接访问慢106%(大约慢一倍)。 预热还需要更长的时间。

这对我来说不是什么大惊喜,因为当我使用OptaPlanner在980个城市中描述(使用抽样)一个人为简单的旅行商问题时,反射成本像拇指酸痛一样突出:

方法句柄

Java 7中引入了MethodHandle来支持invokedynamic指令。 根据javadoc,它是对基础方法的类型化,直接可执行的引用。 听起来很快,对不对?

public final class MyAccessor {private final MethodHandle getterMethodHandle;public MyAccessor() {MethodHandle temp = lookup.findVirtual(Person.class, "getName", MethodType.methodType(String.class));temp = temp.asType(temp.type().changeParameterType(0 , Object.class));getterMethodHandle = temp.asType(temp.type().changeReturnType(Object.class));}public Object executeGetter(Object bean) {return getterMethodHandle.invokeExact(bean);}}

不幸的是, MethodHandle甚至比 OpenJDK 8中的反射还要慢 。它每次操作花费6.1纳秒,因此比直接访问慢132%。

Benchmark           Mode  Cnt  Score   Error  Units
===================================================
DirectAccess        avgt   60  2.667 ± 0.028  ns/op
Reflection          avgt   60  5.511 ± 0.081  ns/op
MethodHandle        avgt   60  6.188 ± 0.059  ns/op
StaticMethodHandle  avgt   60  5.481 ± 0.069  ns/op

话虽如此,如果MethodHandle在静态字段中,则每次操作只需要5.5纳秒,这仍然与反射一样慢 。 此外,对于大多数框架而言,这是无法使用的。 例如,JPA实现可能需要反映n类( PersonCompanyOrder等等)的m getters( getName()getAddress()getBirthDate() ,...),因此JPA实现如何有n * m静态字段,在编译时不知道nm

我确实希望MethodHandle在将来的Java版本中能够像直接访问一样快,从而取代对...的需求。

使用javax.tools.JavaCompiler生成的代码

在Java中,可以在运行时编译和运行生成的Java代码。 因此,使用javax.tools.JavaCompiler API,我们可以在运行时生成直接访问代码:

public abstract class MyAccessor {public static MyAccessor generate() {final String String fullClassName = "x.y.generated.MyAccessorPerson$getName";final String source = "package x.y.generated;\n"+ "public final class MyAccessorPerson$getName extends MyAccessor {\n"+ "    public Object executeGetter(Object bean) {\n"+ "        return ((Person) object).getName();\n"+ "    }\n"+ "}";JavaFileObject fileObject = new ...(fullClassName, source);JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();ClassLoader classLoader = ...;JavaFileManager javaFileManager = new ...(..., classLoader)CompilationTask task = compiler.getTask(..., javaFileManager, ..., singletonList(fileObject));boolean success = task.call();...Class compiledClass = classLoader.loadClass(fullClassName);return compiledClass.newInstance();}// Implemented by the generated subclasspublic abstract Object executeGetter(Object object);}

有关如何使用javax.tools.JavaCompiler更多信息,请参见本文或本文的 第2页 。 除了javax.tools之外,类似的方法也可以使用ASM或CGLIB,但是这些方法会推断出额外的依赖性,并且可能会产生不同的性能结果。

无论如何, 生成的代码与直接访问一样快

Benchmark           Mode  Cnt  Score   Error  Units
===================================================
DirectAccess        avgt   60  2.667 ± 0.028  ns/op
GeneratedCode       avgt   60  2.745 ± 0.025  ns/op

因此,当我再次在OptaPlanner中运行该完全相同的Traveling Salesman问题时,这一次使用代码生成来访问计划变量, 因此总分计算速度提高了18% 。 并且分析(使用采样)看起来也更好:

请注意,在正常使用情况下,由于大量CPU需要实际复杂的分数计算,因此性能提升几乎是无法检测到的...

运行时代码生成的唯一缺点是,它会导致可观的引导成本,特别是如果生成的代码未进行批量编译时。 因此,我仍然希望有一天MethodHandles能够像直接访问一样快,只是为了避免增加引导成本。

结论

在此基准测试中,反射和MethodHandles的速度是OpenJDK 8中直接访问的两倍,但是生成的代码的速度是直接访问的速度。

Benchmark           Mode  Cnt  Score   Error  Units
===================================================
DirectAccess        avgt   60  2.667 ± 0.028  ns/op
Reflection          avgt   60  5.511 ± 0.081  ns/op
MethodHandle        avgt   60  6.188 ± 0.059  ns/op
StaticMethodHandle  avgt   60  5.481 ± 0.069  ns/op
GeneratedCode       avgt   60  2.745 ± 0.025  ns/op

翻译自: https://www.javacodegeeks.com/2018/01/java-reflection-much-faster.html

Java反射,但速度更快相关推荐

  1. java 反射 速度_Java反射,但速度更快

    java 反射 速度 在编译时不知道Java类的最快方法是什么? Java框架通常会这样做. 很多. 它可以直接影响其性能. 因此,让我们对不同的方法进行基准测试,例如反射,方法句柄和代码生成. 用例 ...

  2. java反射机制+继承设计技巧

    [0]README 0.1) 本文描述+源代码均 转自 core java volume 1, 旨在理解 java反射机制 :最后还顺带提出了 继承设计的技巧: [1]反射相关 1)反射定义:能够分析 ...

  3. Java 反射 (快速了解反射)

    反射的概念 JAVA反射机制是在运行状态中,对于任意一个实体类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意方法和属性:这种动态获取信息以及动态调用对象方法的功能称为java ...

  4. java反射最佳实践,java反射性能测试分析

    java反射性能测试分析 java有别于其他编程语言而让我着迷的特性有很多,其中最喜欢的是接口设计,他让我们设计的东西具有美感.同样反射也是我比较喜欢的一个特性,他让程序自动运行,动态加载成为了可能, ...

  5. java 获取 反射 方法 名_乐字节Java反射之一:反射概念与获取反射源头Class

    一.Java反射机制概念 "程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言",如Python, Ruby是动态语言:显然C++,Java,C#不是动态语言,但是JAV ...

  6. Java反射以及应用

    需求:需要通过反射动态获取类的字段类型,然后做特殊处理 Java反射getDeclaredField和getField的区别 getDeclaredFiled 只能获取类本身的属性成员(包括私有.共有 ...

  7. java反射用在哪里_Java反射

    昨天去参加比赛了,所以没有进行博客迁移.人生中的第一场健体比赛,虽然没得奖,但是收获和带来的思考颇丰.意外地进入了男子B组(174以上)的半决赛,然后在半决赛的时候还被裁判员点名出去单独比较,这个很让 ...

  8. Java反射(详述版)

    一.什么是反射? 我们先来看一个例子: package venus; public class Student {public String name;public Student(){System. ...

  9. java 反射 动态代理

    在上一篇文章中介绍Java注解的时候,多次提到了Java的反射API.与javax.lang.model不同的是,通过反射API可以获取程序在运行时刻的内部结构.反射API中提供的动态代理也是非常强大 ...

最新文章

  1. 【廖雪峰python入门笔记】变量
  2. oracle类型不匹配,sys_refcursor的使用,报错类型不匹配
  3. python教程从入门到实践第八章_python:从入门到实践--第八章:函数
  4. GitHub上13个学习资源项目,值得收藏!
  5. Java:抽象类笔记
  6. oracle数据库中的系统自带表情_oracle 系统自带几个常用函数
  7. 【Kafka】KafkaConsumer is not safe for multi-threaded access
  8. 大型网络之---公司内部局域网
  9. 常用animation动画
  10. Python Web编程入门
  11. opencms Log研究
  12. winform自定义panel控件
  13. 详细教您如何把wav转换成mp3格式
  14. 三个技巧教你怎么裁剪视频画面,手残党也能掌握
  15. 实验吧-PHP大法-eregi()函数
  16. 微信群成员活跃度测试软件,微信群活跃度最佳人数是多少?
  17. 安卓控件button添加背景图片
  18. ​从机械工程师到机器学习工程师,我也是个数据科学家了
  19. Jquery颜色选择插件使用
  20. 基于C语言实现的自动打乱九宫格并且还原

热门文章

  1. JVM调优总结(5):典型配置
  2. 浅谈大型网站之负载均衡架构
  3. php artisan快捷命令
  4. 三大框架题目整合考试题(含详解)
  5. 搜索时展示的是名字,传给后端的是id
  6. linux u32,如何在程序中使用u32这个类型啊。
  7. 鸿蒙 电视 安卓,华为鸿蒙2.0来了!打通手机、电视、PC全平台,Mate 40 整装齐发...
  8. java抽取pdf_java 抽取 word,pdf 的四种武器
  9. 基于脚手架创建react项目
  10. GET与POST传递数据的最大长度能够达到多少