深入分析Java反射(八)-优化反射调用性能

简介: Java反射的API在JavaSE1.7的时候已经基本完善,但是本文编写的时候使用的是Oracle JDK11,因为JDK11对于sun包下的源码也上传了,可以直接通过IDE查看对应的源码和进行Debug。

Java反射的API在JavaSE1.7的时候已经基本完善,但是本文编写的时候使用的是Oracle JDK11,因为JDK11对于sun包下的源码也上传了,可以直接通过IDE查看对应的源码和进行Debug。


前提



前一篇文章已经介绍了反射调用的底层原理,其实在实际中对大多数Java使用者来说更关系的是如何提升反射调用的性能,本文主要提供几个可行的方案。另外,由于方法调用时频率最高的反射操作,会着重介绍方法的反射调用优化。


方法一:选择合适的API



选择合适的API主要是在获取反射相关元数据的时候尽量避免使用遍历的方法,例如:


  • 获取Field实例:尽量避免频繁使用Class#getDeclaredFields()或者Class#getFields(),应该根据Field的名称直接调用Class#getDeclaredField()或者Class#getField()
  • 获取Method实例:尽量避免频繁使用Class#getDeclaredMethods()或者Class#getMethods(),应该根据Method的名称和参数类型数组调用Class#getDeclaredMethod()或者Class#getMethod()
  • 获取Constructor实例:尽量避免频繁使用Class#getDeclaredConstructors()或者Class#getConstructors(),应该根据Constructor参数类型数组调用Class#getDeclaredConstructor()或者Class#getConstructor()


其实思路很简单,除非我们想要获取Class的所有Field、Method或者Constructor,否则应该避免使用返回一个集合或者数组的API,这样子能减少遍历或者判断带来的性能损耗。


方法二:缓存反射操作相关元数据



使用缓存机制缓存反射操作相关元数据的原因是因为反射操作相关元数据的实时获取是比较耗时的,这里列举几个相对耗时的场景:


  • 获取Class实例:Class#forName(),此方法可以查看源码,耗时相对其他方法高得多。
  • 获取Field实例:Class#getDeclaredField()Class#getDeclaredFields()Class#getField()Class#getFields()
  • 获取Method实例:Class#getDeclaredMethod()Class#getDeclaredMethods()Class#getMethod()Class#getMethods()
  • 获取Constructor实例:Class#getDeclaredConstructor()Class#getDeclaredConstructors()Class#getConstructor()Class#getConstructors()


这里举个简单的例子,需要反射调用一个普通JavaBean的Setter和Getter方法:


// JavaBean
@Data
public class JavaBean {
    private String name;
}
public class Main {
  private static final Map<Class<?>, List<ReflectionMetadata>> METADATA = new HashMap<>();
  private static final Map<String, Class<?>> CLASSES = new HashMap<>();
  // 解析的时候尽量放在<cinit>里面
  static {
    Class<?> clazz = JavaBean.class;
    CLASSES.put(clazz.getName(), clazz);
    List<ReflectionMetadata> metadataList = new ArrayList<>();
    METADATA.put(clazz, metadataList);
    try {
      for (Field f : clazz.getDeclaredFields()) {
        ReflectionMetadata metadata = new ReflectionMetadata();
        metadataList.add(metadata);
        metadata.setTargetClass(clazz);
        metadata.setField(f);
        String name = f.getName();
        Class<?> type = f.getType();
        metadata.setReadMethod(clazz.getDeclaredMethod(String.format("get%s%s", Character.toUpperCase(name.charAt(0)), name.substring(1))));
        metadata.setWriteMethod(clazz.getDeclaredMethod(String.format("set%s%s", Character.toUpperCase(name.charAt(0)), name.substring(1)), type));
      }
    } catch (Exception e) {
      throw new IllegalStateException(e);
    }
  }
  public static void main(String[] args) throws Exception {
    String fieldName = "name";
    Class<JavaBean> javaBeanClass = JavaBean.class;
    JavaBean javaBean = new JavaBean();
    invokeSetter(javaBeanClass, javaBean, fieldName , "Doge");
    System.out.println(invokeGetter(javaBeanClass,javaBean, fieldName));
    invokeSetter(javaBeanClass.getName(), javaBean, fieldName , "Throwable");
    System.out.println(invokeGetter(javaBeanClass.getName(),javaBean, fieldName));
  }
  private static void invokeSetter(String className, Object target, String fieldName, Object value) throws Exception {
    METADATA.get(CLASSES.get(className)).forEach(each -> {
      Field field = each.getField();
      if (field.getName().equals(fieldName)) {
        try {
          each.getWriteMethod().invoke(target, value);
        } catch (Exception e) {
          throw new IllegalStateException(e);
        }
      }
    });
  }
  private static void invokeSetter(Class<?> clazz, Object target, String fieldName, Object value) throws Exception {
    METADATA.get(clazz).forEach(each -> {
      Field field = each.getField();
      if (field.getName().equals(fieldName)) {
        try {
          each.getWriteMethod().invoke(target, value);
        } catch (Exception e) {
          throw new IllegalStateException(e);
        }
      }
    });
  }
  private static Object invokeGetter(String className, Object target, String fieldName) throws Exception {
    for (ReflectionMetadata metadata : METADATA.get(CLASSES.get(className))) {
      if (metadata.getField().getName().equals(fieldName)) {
        return metadata.getReadMethod().invoke(target);
      }
    }
    throw new IllegalStateException();
  }
  private static Object invokeGetter(Class<?> clazz, Object target, String fieldName) throws Exception {
    for (ReflectionMetadata metadata : METADATA.get(clazz)) {
      if (metadata.getField().getName().equals(fieldName)) {
        return metadata.getReadMethod().invoke(target);
      }
    }
    throw new IllegalStateException();
  }
  @Data
  private static class ReflectionMetadata {
    private Class<?> targetClass;
    private Field field;
    private Method readMethod;
    private Method writeMethod;
  }
}
复制代码


简单来说,解析反射元数据进行缓存的操作最好放在静态代码块或者首次调用的时候(也就是懒加载),这样能够避免真正调用的时候总是需要重新加载一次反射相关元数据。


方法三:反射操作转变为直接调用



"反射操作转变为直接调用"并不是完全不依赖于反射的类库,这里的做法是把反射操作相关元数据直接放置在类的成员变量中,这样就能省去从缓存中读取反射相关元数据的消耗,而所谓"直接调用"一般是通过继承或者实现接口实现。有一些高性能的反射类库也会使用一些创新的方法:例如使用成员属性缓存反射相关元数据,并且把方法调用通过数字建立索引[Number->Method]或者建立索引类(像CGLIBFastClass),这种做法在父类或者接口方法比较少的时候会有一定的性能提升,但是实际上性能评估需要从具体的场景通过测试分析结果而不能盲目使用,使用这个思想的类库有CGLIBReflectASM等。"反射操作转变为直接调用"的最典型的实现就是JDK的动态代理,这里翻出之前动态代理那篇文章的例子来说:


// 接口
public interface Simple {
    void sayHello(String name);
}
// 接口实现
public class DefaultSimple implements Simple {
    @Override
    public void sayHello(String name) {
        System.out.println(String.format("%s say hello!", name));
    }
}
// 场景类
public class Main {
    public static void main(String[] args) throws Exception {
        Simple simple = new DefaultSimple();
        Object target = Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{Simple.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("Before say hello...");
                method.invoke(simple, args);
                System.out.println("After say hello...");
                return null;
            }
        });
        Simple proxy = (Simple) target;
        proxy.sayHello("throwable");
    }
}
// 代理类
public final class $Proxy0 extends Proxy implements Simple {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;
    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }
    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }
    public final void sayHello(String var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }
    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("club.throwable.jdk.sample.reflection.proxy.Simple").getMethod("sayHello", Class.forName("java.lang.String"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
复制代码


这样做的话Simple接口实例虽然最终是通过反射调用sayHello(String var1)方法,但是相关元数据在静态代码块中创建并且已经缓存在类成员属性中,那么反射调用方法的性能已经优化到极致,剩下的都只是Native方法的耗时,这一点使用者在编码层面已经没有办法优化,只能通过升级JVM(JDK)、使用JIT编译器等非编码层面的手段提升反射性能。


小结



本文主要从编码层面分析反射操作一些性能优化的可行经验或者方案,或许有其他更好的优化方案,具体还是需要看使用场景。


个人博客




相关文章
|
5天前
|
XML Java 数据库连接
性能提升秘籍:如何高效使用Java连接池管理数据库连接
在Java应用中,数据库连接管理至关重要。随着访问量增加,频繁创建和关闭连接会影响性能。为此,Java连接池技术应运而生,如HikariCP。本文通过代码示例介绍如何引入HikariCP依赖、配置连接池参数及使用连接池高效管理数据库连接,提升系统性能。
27 5
|
8天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
30 6
|
16天前
|
存储 Java 关系型数据库
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接创建、分配、复用和释放等操作,并通过电商应用实例展示了如何选择合适的连接池库(如HikariCP)和配置参数,实现高效、稳定的数据库连接管理。
34 2
|
19天前
|
Java 数据库连接 数据库
优化之路:Java连接池技术助力数据库性能飞跃
在Java应用开发中,数据库操作常成为性能瓶颈。频繁的数据库连接建立和断开增加了系统开销,导致性能下降。本文通过问题解答形式,深入探讨Java连接池技术如何通过复用数据库连接,显著减少连接开销,提升系统性能。文章详细介绍了连接池的优势、选择标准、使用方法及优化策略,帮助开发者实现数据库性能的飞跃。
25 4
|
16天前
|
存储 Java 开发者
成功优化!Java 基础 Docker 镜像从 674MB 缩减到 58MB 的经验分享
本文分享了如何通过 jlink 和 jdeps 工具将 Java 基础 Docker 镜像从 674MB 优化至 58MB 的经验。首先介绍了选择合适的基础镜像的重要性,然后详细讲解了使用 jlink 构建自定义 JRE 镜像的方法,并通过 jdeps 自动化模块依赖分析,最终实现了镜像的大幅缩减。此外,文章还提供了实用的 .dockerignore 文件技巧和选择安全、兼容的基础镜像的建议,帮助开发者提升镜像优化的效果。
|
16天前
|
Java 数据库连接 数据库
深入探讨Java连接池技术如何通过复用数据库连接、减少连接建立和断开的开销,从而显著提升系统性能
在Java应用开发中,数据库操作常成为性能瓶颈。本文通过问题解答形式,深入探讨Java连接池技术如何通过复用数据库连接、减少连接建立和断开的开销,从而显著提升系统性能。文章介绍了连接池的优势、选择和使用方法,以及优化配置的技巧。
16 1
|
18天前
|
Java 关系型数据库 数据库
面向对象设计原则在Java中的实现与案例分析
【10月更文挑战第25天】本文通过Java语言的具体实现和案例分析,详细介绍了面向对象设计的五大核心原则:单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。这些原则帮助开发者构建更加灵活、可维护和可扩展的系统,不仅适用于Java,也适用于其他面向对象编程语言。
12 2
|
21天前
|
缓存 前端开发 JavaScript
9大高性能优化经验总结,Java高级岗必备技能,强烈建议收藏
关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。本文介绍了9种性能优化方法,涵盖代码优化、数据库优化、连接池调优、架构层面优化、分布式缓存、异步化、Web前端优化、服务化、硬件升级、搜索引擎和产品逻辑优化。欢迎留言交流。
|
21天前
|
存储 缓存 Java
Java应用瘦身记:Docker镜像从674MB优化至58MB的实践指南
【10月更文挑战第22天】 在容器化时代,Docker镜像的大小直接影响到应用的部署速度和运行效率。一个轻量级的Docker镜像可以减少存储成本、加快启动时间,并提高资源利用率。本文将分享如何将一个Java基础Docker镜像从674MB缩减到58MB的实践经验。
34 1
|
8天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。