在编译时不知道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. 汇编语言学习工具Dosbox的安装与配置(一)
  2. python如何连接自己电脑服务器_Python远程连接windows服务器并上传数据
  3. 后端:Spring IOC 知识点总结,写得太好了!
  4. 沙漠上不小心挖了个洞,让这个地狱之门般的巨坑,燃烧了50年
  5. Java自动化邮件中发送图表(四)之javafx Chart
  6. Python之subprocess模块
  7. C#设计模式系列 8 ----Builder 生成器模式之--发工资了,带老婆到 岗顶百脑汇配置电脑...
  8. 流行的Spring Boot + Vue架构整合开发的网易云,附源码!
  9. vue-video-player 断点续播
  10. 2018:平凡而不平庸的一年
  11. win10偶尔打不开开始菜单(按win键和点击开始菜单都没反应)
  12. 如何从一个大规模的文本中筛选出符合条件的记录
  13. MATLAB基本使用方法
  14. 斗地主机器人智能算法深度研究
  15. 计算机网络基础【2】
  16. 如何正确地学习Java
  17. 44 - 操作MySQL数据库
  18. 参加SODA数据比赛
  19. c语言程序结构设计教案,大学课程-c语言选择结构程序设计教案
  20. 物业管理系统设计与实现

热门文章

  1. Vue.js2.0开发环境搭建(三)
  2. Jsoup代码解读之一-概述
  3. 请用JavaScript实现一个函数,接受一-个IP白名单列表whitelist以及列表ipList
  4. ssm使用全注解实现增删改查案例——DeptServiceImpl
  5. javaWeb服务详解(含源代码,测试通过,注释) ——Emp的Dao层
  6. C++比较两个字符串是否完全相同
  7. vmware启动多个虚拟机
  8. 字节数组转jsonobject(如读取HttpServletRequest.inputstream到jsonobject)
  9. 用jackson转json_用Jackson编写大JSON文件
  10. yeoman_具有Spring Boot和Yeoman的单页Angularjs应用程序