通过reflection调用Java中的getter:重复调用它的最快方法是什么(性能和可伸缩性方面)?

给定一个类Foo和一个属性bar我在编译时都不知道 ,我需要多次重复调用getter Foo.getBar()

假设我有:

 Method barGetterMethod = ...; // Don't worry how I got this 

我需要做这样的事情:

 for (Object foo : fooList) { // 1000000000 elements in fooList Object bar = barGetterMethod.invoke(foo); ... } 

与没有reflection调用它相比,上面的实现仍然很慢。 有更快的方法吗?

用Javareflection调用getter的最快方法是什么?

您可以使用MethodHandle 。 它的Javadoc写道:

使用Lookup API中的工厂方法,可以将Core Reflection API对象表示的任何类成员转换为行为等效的方法句柄。 例如,可以使用Lookup.unreflect将reflection方法转换为方法句柄。 生成的方法句柄通常提供对底层类成员的更直接和有效的访问。

虽然这会减少开销,但是如果使用通常的(非reflection)字节代码指令进行调用,方法句柄仍然会阻止JVM可以采用的某些优化(例如内联方法)。 这种优化是否有益取决于您如何使用该方法(如果该代码路径始终调用相同的方法,则内联可以提供帮助,如果它每次都是不同的方法,可能不会)。

以下微基准测试可能会让您大致了解reflection,方法句柄和直接调用的相对性能:

 package tools.bench; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.reflect.Method; import java.math.BigDecimal; public abstract class Bench { final String name; public Bench(String name) { this.name = name; } abstract int run(int iterations) throws Throwable; private BigDecimal time() { try { int nextI = 1; int i; long duration; do { i = nextI; long start = System.nanoTime(); run(i); duration = System.nanoTime() - start; nextI = (i << 1) | 1; } while (duration < 100000000 && nextI > 0); return new BigDecimal((duration) * 1000 / i).movePointLeft(3); } catch (Throwable e) { throw new RuntimeException(e); } } @Override public String toString() { return name + "\t" + time() + " ns"; } static class C { public Integer foo() { return 1; } } static final MethodHandle sfmh; static { try { Method m = C.class.getMethod("foo"); sfmh = MethodHandles.lookup().unreflect(m); } catch (Exception e) { throw new RuntimeException(e); } } public static void main(String[] args) throws Exception { final C invocationTarget = new C(); final Method m = C.class.getMethod("foo"); final Method am = C.class.getMethod("foo"); am.setAccessible(true); final MethodHandle mh = sfmh; Bench[] marks = { new Bench("reflective invocation (without setAccessible)") { @Override int run(int iterations) throws Throwable { int x = 0; for (int i = 0; i < iterations; i++) { x += (Integer) m.invoke(invocationTarget); } return x; } }, new Bench("reflective invocation (with setAccessible)") { @Override int run(int iterations) throws Throwable { int x = 0; for (int i = 0; i < iterations; i++) { x += (Integer) am.invoke(invocationTarget); } return x; } }, new Bench("methodhandle invocation") { @Override int run(int iterations) throws Throwable { int x = 0; for (int i = 0; i < iterations; i++) { x += (Integer) mh.invokeExact(invocationTarget); } return x; } }, new Bench("static final methodhandle invocation") { @Override int run(int iterations) throws Throwable { int x = 0; for (int i = 0; i < iterations; i++) { x += (Integer) sfmh.invokeExact(invocationTarget); } return x; } }, new Bench("direct invocation") { @Override int run(int iterations) throws Throwable { int x = 0; for (int i = 0; i < iterations; i++) { x += invocationTarget.foo(); } return x; } }, }; for (Bench bm : marks) { System.out.println(bm); } } } 

在我有点过时的笔记本上

 java version "1.7.0_02" Java(TM) SE Runtime Environment (build 1.7.0_02-b13) Java HotSpot(TM) Client VM (build 22.0-b10, mixed mode, sharing) 

这打印:

 reflective invocation (without setAccessible) 568.506 ns reflective invocation (with setAccessible) 42.377 ns methodhandle invocation 27.461 ns static final methodhandle invocation 9.402 ns direct invocation 9.363 ns 

更新:正如Irreputable指出的那样,服务器VM具有一些不同的性能特征,因此在服务器VM中使用MethodHandle只有在将其置于静态最终字段时才有用 ,在这种情况下,VM可以内联调用:

 reflective invocation (without setAccessible) 9.736 ns reflective invocation (with setAccessible) 7.113 ns methodhandle invocation 26.319 ns static final methodhandle invocation 0.045 ns direct invocation 0.044 ns 

我建议您测量您的特定用例。

调用barReadMethod.setAccessible(true); 关闭安全检查,这可以使它更快一点。 即使它是可访问的,也必须另外检查。

如果我运行使用带有和不带true的getter方法。

 class Main { static class A { private final Integer i; A(Integer i) { this.i = i; } public Integer getI() { return i; } } public static void main(String... args) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException { A[] as = new A[100000]; for (int i = 0; i < as.length; i++) as[i] = new A(i); for (int i = 0; i < 5; i++) { long time1 = timeSetAccessible(as); long time2 = timeNotSetAccessible(as); System.out.printf("With setAccessible true %.1f ns, Without setAccessible %.1f ns%n", (double) time1 / as.length, (double) time2 / as.length); } } static long dontOptimiseAvay = 0; private static long timeSetAccessible(A[] as) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException { Method getter = A.class.getDeclaredMethod("getI"); getter.setAccessible(true); dontOptimiseAvay = 0; long start = System.nanoTime(); for (A a : as) { dontOptimiseAvay += (Integer) getter.invoke(a); } return System.nanoTime() - start; } private static long timeNotSetAccessible(A[] as) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException { Method getter = A.class.getDeclaredMethod("getI"); // getter.setAccessible(true); dontOptimiseAvay = 0; long start = System.nanoTime(); for (A a : as) { dontOptimiseAvay += (Integer) getter.invoke(a); } return System.nanoTime() - start; } } 

版画

 With setAccessible true 106.4 ns, Without setAccessible 126.9 ns With setAccessible true 5.4 ns, Without setAccessible 29.4 ns With setAccessible true 3.2 ns, Without setAccessible 9.9 ns With setAccessible true 3.1 ns, Without setAccessible 9.0 ns With setAccessible true 3.1 ns, Without setAccessible 8.9 ns 

对于简单的getter,使用setAccessible(true)可以快三倍。

如果上面讨论的静态最终MethodHandle选项不实用/可能,另一个选项是使用bytebuddy动态生成一个类,它有一个方法接受foo,在foo上调用bar方法并返回结果。

这将提供单独调用bar的性能与直接调用基本相同(包装调用最终可能是内联的)。

但是,这会产生1次为类和方法生成字节代码的成本。 根据bytebuddy网站,这个成本在200ns附近。