Java反射性能分析及优化

Java反射性能分析及优化

一说到反射,我们经常听到”反射调用慢”,”反射性能差,尽量少使用”这类言论,那我们有没有想过反射性能差,那到底差多少呢?性能差原因是什么?有没有优化的空间呢?

1.Java反射性能到底差多少?
有比较才有鉴别,我们说反射性能差,其实是相对的,是相对于直接调用而言的。那我们分别写个直接调用和反射的例子来比较一下,反射的性能比直接调用到底差多少?

public class Animal {

public double run(double duration){
double distance = 30 * duration;
return distance;
}
}
直接调用:

public class DirectExample {

public static void main(String[] args) {
Animal tomcat = new Animal();
long startTime = System.nanoTime();
for(int i = 0; i < 10000; i++) {
double distance = tomcat.run(2d * i);
}
long endTime = System.nanoTime();
System.out.println("调用耗时:" + (endTime - startTime) + "ns");
}
}
反射调用:

public class StandardReflectExample {

public static void main(String[] args) throws Exception{
String className = "com.dblones.java.reflect.example01.Animal";
Class clazz = Class.forName(className);
Constructor constructor = clazz.getConstructor();
Object obj = constructor.newInstance();
long startTime = System.nanoTime();
for(int i = 0; i < 10000; i++) {
Method method = clazz.getMethod("run", double.class);
Object distance = (Object) method.invoke(obj, 2d*i);
}
long endTime = System.nanoTime();
System.out.println("调用耗时:" + (endTime - startTime));
}
}
各执行3次,结果如下:

直接调用:

1,219900ns

2,228800ns

3,276300ns

反射调用:

1,15172800ns

2,15443500ns

3,13531200ns


反射调用耗时大约是直接调用的60倍。考虑到method我们可以缓存起来,不用每次调用的时候都去取一次,我们调整一下,再来比较一下。

public class StandardReflectExample {

public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
String className = "com.dblones.java.reflect.example01.Animal";
Class clazz = Class.forName(className);
Constructor constructor = clazz.getConstructor();
Method method = clazz.getMethod("run", double.class);
Object obj = constructor.newInstance();
long startTime = System.nanoTime();
for(int i = 0; i < 10000; i++) {
Object distance = (Object) method.invoke(obj, 2d*i);
}
long endTime = System.nanoTime();
System.out.println("调用耗时:" + (endTime - startTime));
}
}
1, 6657900ns

2, 5912100ns

3, 5975400ns


缓存反射调用耗时大约是直接调用的25倍。

也就是说,如果不做大的优化的话,如果能缓存Method对象的话,反射耗时大约是直接调用的25倍,如果不能缓存Method对象,例如代理,AOP等场景下,那么反射耗时大约是直接调用的60倍。那我们来分析一下反射为什么这么慢?

2.Java反射性能差的原因
性能差是相对的,是相对于我们直接调用,直接调用的时候,是静态的(可能有人会问,什么时候静态的,就是实例类型,方法名,参数都是确定的),编译阶段编译器会做权限,可见性,参数等检验,加载阶段解析的时候,就会方法对应的符号引用转换为地址引用,到我们执行方法调用时,就可以直接新建栈帧进行方法调用了。但是反射调用的过程中,是动态的,在执行的时候才明确下来,所以会存在一些验证以及一些安全机制的考虑,另外就是因为是动态的,所以可能会存在一些JVM无法优化的因素。

2.1原因总结
分析完Java反射过程之后,我们先上结论:

Java 反射性能差主要原因是:

2.1.1.获取Method对象慢
2.1.1.1需要检查方法权限
2.1.1.2需要遍历筛选递归
2.1.1.3每一个Method都有一个root,不暴漏给外部,而是每次copy一个Method
2.1.2.调用invoke方法慢
2.1.2.1 Method#invoke 方法会对参数做封装和解封操作
2.1.2.2 需要检查方法可见性
2.1.2.3 需要校验参数
2.1.2.4 invoke调用逻辑是委托给MethodAccessor的,而accessor对象会在第一次invoke的时候才创建,是一种lazy init方式
2.1.2.5 反射方法难以内联
2.1.2.6 JIT 无法优化
2.2分析获取Method对象
我们先看获取Method对象的过程

class Class {
@CallerSensitive
public Method getMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
Objects.requireNonNull(name);
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// 1. 检查方法权限
checkMemberAccess(sm, Member.PUBLIC, Reflection.getCallerClass(), true);
}
// 2. 获取方法
Method method = getMethod0(name, parameterTypes);
if (method == null) {
throw new NoSuchMethodException(methodToString(name, parameterTypes));
}
// 3. 返回方法的拷贝
return getReflectionFactory().copyMethod(method);
}

@CallerSensitive
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
Objects.requireNonNull(name);
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// 1. 检查方法是权限
checkMemberAccess(sm, Member.DECLARED, Reflection.getCallerClass(), true);
}
// 2. 获取方法
Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
if (method == null) {
throw new NoSuchMethodException(methodToString(name, parameterTypes));
}
// 3. 返回方法的拷贝
return getReflectionFactory().copyMethod(method);
}
}
这2个方法获取Method对象的过程大体上差不多,都是:

1.检查方法权限

2.获取方法 Method 对象

3.返回方法的拷贝

这里主要有两个区别:

getMethod 中 checkMemberAccess 传入的是 Member.PUBLIC,而 getDeclaredMethod 传入的是 Member.DECLARED
这两个值有什么区别呢?我们看下代码中的注释:

interface Member {
/**
* Identifies the set of all public members of a class or interface,
* including inherited members.
*/
public static final int PUBLIC = 0;

/**
* Identifies the set of declared members of a class or interface.
* Inherited members are not included.
*/
public static final int DECLARED = 1;
}
注释里面解释了 PUBLIC 和 DECLARED 的含义,就是PUBLIC 会包括所有的 public 方法,包括父类的方法,而 DECLARED 会包括所有自己定义的方法,public,protected,private 都在此,但是不包括父类的方法。

getMethod 中获取方法调用的是 getMethod0,而 getDeclaredMethod获取方法调用的是 searchMethods。
关于这个区别,这里简单提及一下,后面具体分析代码。

我们上面看了 getMethod 和 getDeclaredMethod 的区别,我们先选择 getMethod 方法进行分析,走完整个流程。

@CallerSensitive
public Method getMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
Objects.requireNonNull(name);
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// 1. 检查方法权限
checkMemberAccess(sm, Member.PUBLIC, Reflection.getCallerClass(), true);
}
// 2. 获取方法
Method method = getMethod0(name, parameterTypes);
if (method == null) {
throw new NoSuchMethodException(methodToString(name, parameterTypes));
}
// 3. 返回方法的拷贝
return getReflectionFactory().copyMethod(method);
}
我们首先来看第一个步骤

2.2.1.检查方法权限checkMemberAccess方法
如果不允许调用线程访问成员,则该方法抛出SecurityException。 默认策略是允许访问PUBLIC成员,以及访问与调用者具有相同类加载器的类。 在所有其他情况下,此方法使用RuntimePermission(“accessDeclaredMembers”)权限调用checkPermission。

方法整体是在检查是否可以访问对象成员,我们就不继续详细看了。

2.2.2.获取方法getMethod0方法
private Method getMethod0(String name, Class<?>[] parameterTypes) {
PublicMethods.MethodList res = getMethodsRecursive(
name,
parameterTypes == null ? EMPTY_CLASS_ARRAY : parameterTypes,
/* includeStatic */ true);
return res == null ? null : res.getMostSpecific();
}
这里是通过 getMethodsRecursive 获取到 MethodList 对象,然后通过 MethodList#getMostSpecific 方法筛选出对应的方法。MethodList#getMOstSpecific 会筛选返回值类型最为具体的方法,至于为什么会有返回值的区别,后面会讲到。(这里的具体,指的是有两个方法,返回值分别是 Child 和 Parent,Child 继承自 Parent,这里会筛选出返回值为 Child 的方法)。

接着看 getMethodsRecursive 方法,是如何获取方法的。

private PublicMethods.MethodList getMethodsRecursive(String name,
Class<?>[] parameterTypes,
boolean includeStatic) {
// 1. 获取自己声明的 public 方法
Method[] methods = privateGetDeclaredMethods(/* publicOnly */ true);
// 2. 筛选符合条件的方法,根据方法名,参数类筛选,包括静态方法,构造 MethodList 对象
PublicMethods.MethodList res = PublicMethods.MethodList
.filter(methods, name, parameterTypes, includeStatic);
// 3. 如果找到方法不为空,直接返回
if (res != null) {
return res;
}
Class<?> sc = getSuperclass();
if (sc != null) {
// 4. 没有找到方法,就获取其父类,递归调用 getMethodsRecursive 方法
res = sc.getMethodsRecursive(name, parameterTypes, includeStatic);
}
// 5. 遍历接口,获取接口中对应的方法并合并到methodList
for (Class<?> intf : getInterfaces(/* cloneArray */ false)) {
res = PublicMethods.MethodList.merge(
res, intf.getMethodsRecursive(name, parameterTypes,
/* includeStatic */ false));
}
return res;
}

private Method[] privateGetDeclaredMethods(boolean publicOnly) {
Method[] res;
// 1. 通过缓存获取 Method[]
ReflectionData<T> rd = reflectionData();
if (rd != null) {
res = publicOnly ? rd.declaredPublicMethods : rd.declaredMethods;
if (res != null) return res;
}
// 2. 没有缓存,通过 JVM 获取(getDeclaredMethods0是native方法,通过JVM实现)
res = Reflection.filterMethods(this, getDeclaredMethods0(publicOnly));
if (rd != null) {
if (publicOnly) {
rd.declaredPublicMethods = res;
} else {
rd.declaredMethods = res;
}
}
return res;
}
2.2.3 返回方法的拷贝getReflectionFactory().copyMethod()方法
public Method copyMethod(Method arg) {
return langReflectAccess().copyMethod(arg);
}
public Method copyMethod(Method arg) {
return arg.copy();
}
Method copy() {
if (this.root != null)
throw new IllegalArgumentException("Can not copy a non-root Method");

Method res = new Method(clazz, name, parameterTypes, returnType,
exceptionTypes, modifiers, slot, signature,
annotations, parameterAnnotations, annotationDefault);
res.root = this;
// Might as well eagerly propagate this if already present
// 如果methodAccessor对象已经存在的话,那就早早的设置好,如果是从缓存里面获取的Method对象,则有值,如果是从JVM获取则为空
res.methodAccessor = methodAccessor;
return res;
}
new 一个 Method 实例并返回。这里有两点要注意:

设置 root = this

会给 Method 设置 MethodAccessor,用于后面方法调用。也就是所有的 Method 的拷贝都会使用同一份 methodAccessor。

我们再来看看getDeclaredMethod方法

@CallerSensitive
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
Objects.requireNonNull(name);
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// 1. 检查方法是权限
checkMemberAccess(sm, Member.DECLARED, Reflection.getCallerClass(), true);
}
// 2. 获取方法
Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
if (method == null) {
throw new NoSuchMethodException(methodToString(name, parameterTypes));
}
// 3. 返回方法的拷贝
return getReflectionFactory().copyMethod(method);
}
就剩下一个searchMethods方法

private static Method searchMethods(Method[] methods,
String name,
Class<?>[] parameterTypes){
ReflectionFactory fact = getReflectionFactory();
Method res = null;
for (Method m : methods) {
// 比较方法名
if (m.getName().equals(name)
// 比较方法参数
&& arrayContentsEq(parameterTypes,
fact.getExecutableSharedParameterTypes(m))
// 比较返回值
&& (res == null
|| (res.getReturnType() != m.getReturnType()
&& res.getReturnType().isAssignableFrom(m.getReturnType()))))
res = m;
}
return res;
}
searchMethods 方法实现比较简单,就是对比方法名,参数,方法返回值。

2.3 分析调用invoke方法
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException{
if (!override) {
Class<?> caller = Reflection.getCallerClass();
// 1. 检查权限
checkAccess(caller, clazz,
Modifier.isStatic(modifiers) ? null : obj.getClass(),
modifiers);
}
// 2. 获取 MethodAccessor
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
// 创建 MethodAccessor
ma = acquireMethodAccessor();
}
// 3. 调用 MethodAccessor.invoke
return ma.invoke(obj, args);
}
2.3.1检查权限checkAccess()
这里对 override 变量进行判断,如果 override == true,就跳过检查 我们通常在 Method#invoke 之前,会调用 Method#setAccessible(true),就是设置 override 值为 true。

2.3.2 获取 MethodAccessor或创建MethodAccessor(调用acquireMethodAccessor()方法)
首次调用的时候methodAccessor为空,需要调用acquireMethodAccessor()方法创建MethodAccessor对象。

private MethodAccessor acquireMethodAccessor() {
// First check to see if one has been created yet, and take it
// if so
MethodAccessor tmp = null;
if (root != null) tmp = root.getMethodAccessor();
if (tmp != null) {
methodAccessor = tmp;
} else {
// Otherwise fabricate one and propagate it up to the root
tmp = reflectionFactory.newMethodAccessor(this);
setMethodAccessor(tmp);
}

return tmp;
}

public MethodAccessor newMethodAccessor(Method method) {
// 其中会对 noInflation 进行赋值
checkInitted();
// 测试给定的方法是否对调用方敏感,并且声明的类是否由引导类装入器或平台类装入器定义
if (Reflection.isCallerSensitive(method)) {
Method altMethod = findMethodForReflection(method);
if (altMethod != null) {
method = altMethod;
}
}
//
Method root = langReflectAccess.getRoot(method);
if (root != null) {
method = root;
}
//
if (noInflation && !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {
// 生成的是 MethodAccessorImpl
return new MethodAccessorGenerator().
generateMethod(method.getDeclaringClass(),
method.getName(),
method.getParameterTypes(),
method.getReturnType(),
method.getExceptionTypes(),
method.getModifiers());
} else {
// 生成的是 NativeMethodAccessorImpl
NativeMethodAccessorImpl acc =
new NativeMethodAccessorImpl(method);
DelegatingMethodAccessorImpl res =
new DelegatingMethodAccessorImpl(acc);
acc.setParent(res);
return res;
}
}
这里可以看到,一共有三种 MethodAccessor。MethodAccessorImpl,NativeMethodAccessorImpl,DelegatingMethodAccessorImpl。采用哪种 MethodAccessor 根据 noInflation 进行判断,noInflation 默认值为 false,只有指定了 sun.reflect.noInflation 属性为 true,才会 采用 MethodAccessorImpl。所以默认会调用 NativeMethodAccessorImpl。

MethodAccessorImpl 是通过动态生成字节码来进行方法调用的,是 Java 版本的 MethodAccessor,字节码生成比较复杂,这里不放代码了。大家感兴趣可以看这里的 generate 方法。

DelegatingMethodAccessorImpl 就是单纯的代理,真正的实现还是 NativeMethodAccessorImpl。

NativeMethodAccessorImpl 是 Native 版本的 MethodAccessor 实现。

class DelegatingMethodAccessorImpl extends MethodAccessorImpl {
private MethodAccessorImpl delegate;

DelegatingMethodAccessorImpl(MethodAccessorImpl delegate) {
setDelegate(delegate);
}

public Object invoke(Object obj, Object[] args)
throws IllegalArgumentException, InvocationTargetException
{
return delegate.invoke(obj, args);
}

void setDelegate(MethodAccessorImpl delegate) {
this.delegate = delegate;
}
}

class NativeMethodAccessorImpl extends MethodAccessorImpl {
public Object invoke(Object obj, Object[] args)
throws IllegalArgumentException, InvocationTargetException
{
// We can't inflate methods belonging to vm-anonymous classes because
// that kind of class can't be referred to by name, hence can't be
// found from the generated bytecode.
if (++numInvocations > ReflectionFactory.inflationThreshold()
&& !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {
// Java 版本的 MethodAccessor
MethodAccessorImpl acc = (MethodAccessorImpl)
new MethodAccessorGenerator().
generateMethod(method.getDeclaringClass(),
method.getName(),
method.getParameterTypes(),
method.getReturnType(),
method.getExceptionTypes(),
method.getModifiers());
parent.setDelegate(acc);
}

// Native 版本调用
return invoke0(method, obj, args);
}

private static native Object invoke0(Method m, Object obj, Object[] args);
}
在 NativeMethodAccessorImpl 的实现中,我们可以看到,有一个 numInvocations 阀值控制,numInvocations 表示调用次数。如果 numInvocations 大于 15(默认阀值是 15),那么就使用 Java 版本的 MethodAccessorImpl。

为什么采用这个策略呢,可以通过 JDK 中的注释来看。

public class ReflectionFactory {
// "Inflation" mechanism. Loading bytecodes to implement
// Method.invoke() and Constructor.newInstance() currently costs
// 3-4x more than an invocation via native code for the first
// invocation (though subsequent invocations have been benchmarked
// to be over 20x faster). Unfortunately this cost increases
// startup time for certain applications that use reflection
// intensively (but only once per class) to bootstrap themselves.
// To avoid this penalty we reuse the existing JVM entry points
// for the first few invocations of Methods and Constructors and
// then switch to the bytecode-based implementations.
//
// Package-private to be accessible to NativeMethodAccessorImpl
// and NativeConstructorAccessorImpl
private static boolean noInflation = false;
}
Java 版本的 MethodAccessorImpl 调用效率比 Native 版本要快 20 倍以上,但是 Java 版本加载时要比 Native 多消耗 3-4 倍资源,所以默认会调用 Native 版本,如果调用次数超过 15 次以后,就会选择运行效率更高的 Java 版本。那为什么 Native 版本运行效率会没有 Java 版本高呢?从 R 大博客来看,是因为 这是HotSpot的优化方式带来的性能特性,同时也是许多虚拟机的共同点:跨越native边界会对优化有阻碍作用,它就像个黑箱一样让虚拟机难以分析也将其内联,于是运行时间长了之后反而是托管版本的代码更快些。

2.3.3 调用MethodAccessor.invoke方法
在生成 MethodAccessor 以后,就调用其 invoke 方法进行最终的反射调用。这里我们对 Java 版本的 MethodAccessorImpl 做个简单的分析,Native 版本暂时不做分析。在前面我们提到过 MethodAccessorImpl 是通过 MethodAccessorGenerator#generate 生成动态字节码然后动态加载到 JVM 中的。其中生成 invoke 方法字节码的是 MethodAccessorGenerator#emitInvoke。

到这里,整个Java反射调用过程就走完了。

我们再来回顾一下我们开头给的原因总结:

Java 反射性能差主要原因是:

1.获取Method对象慢

1.1 需要检查方法权限

每次获取Method对象,都需要检查方法权限是否合法,哪怕是已经调用的Method对象。

1.2 需要遍历筛选递归

每次都需要遍历筛选才能定位获取到Method对象,有时候甚至还需要递归向父类和接口遍历筛选获取Method对象

1.3 每一个Method都有一个root,不暴漏给外部,而是每次copy一个Method

每次都要拷贝一个Method对象

2.调用invoke方法慢

2.1 Method#invoke 方法会对参数做封装和解封操作

invoke 方法的参数是 Object[] 类型,也就是说,如果方法参数是简单类型的话,需要在此转化成 Object 类型,例如 long ,在 javac compile 的时候 用了Long.valueOf() 转型,也就大量了生成了Long 的 Object, 同时 传入的参数是Object[]数值,那还需要额外封装object数组。而在上面 MethodAccessorGenerator#emitInvoke 方法里我们看到,生成的字节码时,会把参数数组拆解开来,把参数恢复到没有被 Object[] 包装前的样子,同时还要对参数做校验,这里就涉及到了解封操作。因此,在反射调用的时候,因为封装和解封,产生了额外的不必要的内存浪费,当调用次数达到一定量的时候,还会导致 GC。

2.2 需要检查方法可见性

每次调用都必须检查方法的可见性(在 Method.invoke 里)

2.3 需要校验参数

每次调用都要检查每个实际参数与形式参数的类型匹配性(在NativeMethodAccessorImpl.invoke0 里或者生成的 Java 版 MethodAccessor.invoke 里)

2.4 invoke调用逻辑是委托给MethodAccessor的,而accessor对象会在第一次invoke的时候才创建,是一种lazy init方式

2.5 反射方法难以内联

内联概念:把函数调用的方法直接内嵌到方法内部,减少函数调用的次数。

native版的反射调用则无法被有效内联,因而调用开销无法随程序的运行而降低。

2.6 JIT 无法优化

因为反射涉及到动态加载的类型,所以无法进行优化。

3.Java反射性能优化方案
3.1 验证NativeMethodAccessorImpl及Java 版 MethodAccessor
public class A {
public void foo(String name) {
System.out.println("Hello, " + name);
}
}

import java.lang.reflect.Method;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class TestClassLoad {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("A");
Constructor constructor = clazz.getConstructor();
Object obj = constructor.newInstance();
Method method = clazz.getMethod("foo", String.class);
for (int i = 0; i < 16; i++) {
method.invoke(obj, Integer.toString(i));
}
}
}
java -verbose:class TestClassLoad

执行后,查看后台打印信息如下:

Hello, 0
Hello, 1
Hello, 2
Hello, 3
Hello, 4
Hello, 5
Hello, 6
Hello, 7
Hello, 8
Hello, 9
Hello, 10
Hello, 11
Hello, 12
Hello, 13
Hello, 14
[0.188s][info][class,load] sun.reflect.misc.ReflectUtil source: jrt:/java.base
[0.188s][info][class,load] jdk.internal.reflect.ClassFileConstants source: jrt:/java.base
[0.188s][info][class,load] jdk.internal.reflect.AccessorGenerator source: jrt:/java.base
[0.188s][info][class,load] jdk.internal.reflect.MethodAccessorGenerator source: jrt:/java.base
[0.188s][info][class,load] jdk.internal.reflect.ByteVectorFactory source: jrt:/java.base
[0.188s][info][class,load] jdk.internal.reflect.ByteVector source: jrt:/java.base
[0.188s][info][class,load] jdk.internal.reflect.ByteVectorImpl source: jrt:/java.base
[0.188s][info][class,load] jdk.internal.reflect.ClassFileAssembler source: jrt:/java.base
[0.189s][info][class,load] jdk.internal.reflect.UTF8 source: jrt:/java.base
[0.189s][info][class,load] jdk.internal.reflect.Label source: jrt:/java.base
[0.189s][info][class,load] jdk.internal.reflect.Label$PatchInfo source: jrt:/java.base
[0.189s][info][class,load] jdk.internal.reflect.MethodAccessorGenerator$1 source: jrt:/java.base
[0.190s][info][class,load] jdk.internal.reflect.ClassDefiner source: jrt:/java.base
[0.190s][info][class,load] jdk.internal.reflect.ClassDefiner$1 source: jrt:/java.base
[0.190s][info][class,load] jdk.internal.reflect.GeneratedMethodAccessor1 source: __JVM_DefineClass__
Hello, 15
前面15次调用使用NativeMethodAccessorImpl,到16次时,超过阈值,调用MethodAccessorGenerator.generateMethod()来生成Java版的MethodAccessor的实现类,并且改变DelegatingMethodAccessorImpl所引用的MethodAccessor为Java版。触发了类加载机制,所以我们就看到15次和16次调用之间新加载的类。跟前面的分析结果一致。

3.2 缓存Method
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class StandardReflectExample {

public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
String className = "com.dblones.java.reflect.example01.Animal";
Class clazz = Class.forName(className);
Constructor constructor = clazz.getConstructor();
Method method = clazz.getMethod("run", double.class);
Object obj = constructor.newInstance();
long startTime = System.nanoTime();
for(int i = 0; i < 10000; i++) {
Object distance = (Object) method.invoke(obj, 2d*i);
}
long endTime = System.nanoTime();
System.out.println("调用耗时:" + (endTime - startTime));
}
}
3.3 设置检查方法的可见性为true
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class StandardReflectExample {

public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
String className = "com.dblones.java.reflect.example01.Animal";
Class clazz = Class.forName(className);
Constructor constructor = clazz.getConstructor();
Method method = clazz.getMethod("run", double.class);
method.setAccessible(true);
Object obj = constructor.newInstance();
long startTime = System.nanoTime();
for(int i = 0; i < 10000; i++) {
Object distance = (Object) method.invoke(obj, 2d*i);
}
long endTime = System.nanoTime();
System.out.println("调用耗时:" + (endTime - startTime));
}
}
3.4 如果可以缓存Method的情况,提前设置好MethodAccessor为Java 版 MethodAccessor
import jdk.internal.reflect.MethodAccessor;
import jdk.internal.reflect.ReflectionFactory;
import sun.misc.Unsafe;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class UnsafeExample {

static final Unsafe unsafe;

static {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
} catch (Exception ex) {
throw new Error(ex);
}
}

public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
String className = "com.dblones.java.reflect.example01.Animal";
Class clazz = Class.forName(className);
Constructor constructor = clazz.getConstructor();
Method method = clazz.getMethod("run", double.class);
method.setAccessible(true);
long noInflationOffset = unsafe.staticFieldOffset(ReflectionFactory.class.getDeclaredField("noInflation"));
Object reflectionFactoryClass = unsafe.staticFieldBase(ReflectionFactory.class.getDeclaredField("noInflation"));
unsafe.putBoolean(reflectionFactoryClass, noInflationOffset, true);
long methodAccessorOffset = unsafe.objectFieldOffset(Method.class.getDeclaredField("methodAccessor"));
ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();
MethodAccessor methodAccessor = reflectionFactory.newMethodAccessor(method);
unsafe.putObject(method, methodAccessorOffset, methodAccessor);
Object obj = constructor.newInstance();
long startTime = System.nanoTime();
for(int i = 0; i < 10000; i++) {
Object distance = (Object) method.invoke(obj, 2d*i);
}
long endTime = System.nanoTime();
System.out.println("调用耗时:" + (endTime - startTime));
}
}
3.5 如果可以缓存Method的情况,绕过Method,直接使用Java 版 MethodAccessor
import jdk.internal.reflect.MethodAccessor;
import jdk.internal.reflect.ReflectionFactory;
import sun.misc.Unsafe;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class UnsafeExample {

static final Unsafe unsafe;

static {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
} catch (Exception ex) {
throw new Error(ex);
}
}

public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
String className = "com.dblones.java.reflect.example01.Animal";
Class clazz = Class.forName(className);
Constructor constructor = clazz.getConstructor();
Method method = clazz.getMethod("run", double.class);
method.setAccessible(true);
long noInflationOffset = unsafe.staticFieldOffset(ReflectionFactory.class.getDeclaredField("noInflation"));
Object reflectionFactoryClass = unsafe.staticFieldBase(ReflectionFactory.class.getDeclaredField("noInflation"));
unsafe.putBoolean(reflectionFactoryClass, noInflationOffset, true);
ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();
MethodAccessor methodAccessor = reflectionFactory.newMethodAccessor(method);
Object obj = constructor.newInstance();
long startTime = System.nanoTime();
for(int i = 0; i < 10000; i++) {
Object distance = (Object) methodAccessor.invoke(obj, new Object[]{2d*i});
}
long endTime = System.nanoTime();
System.out.println("调用耗时:" + (endTime - startTime));
}
}
3.6 使用AsmReflect包
3.6.1 提前算好方法位置
import com.esotericsoftware.reflectasm.MethodAccess;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class AsmReflectExample {

public static void main(String[] args) throws Throwable {
String className = "com.dblones.java.reflect.example01.Animal";
Class clazz = Class.forName(className);
Constructor constructor = clazz.getConstructor();
MethodAccess access = MethodAccess.get(clazz);
Integer index = access.getIndex("run", double.class);
Object obj = constructor.newInstance();
long startTime = System.nanoTime();
for(int i = 0; i < 10000; i++) {
Object distance = access.invoke(obj, index, 2d*i);
}
long endTime = System.nanoTime();
System.out.println("调用耗时:" + (endTime - startTime));
}
}
3.6.2 不做任何限定的调用
import com.esotericsoftware.reflectasm.MethodAccess;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class AsmReflectExample {

public static void main(String[] args) throws Throwable {
String className = "com.dblones.java.reflect.example01.Animal";
Class clazz = Class.forName(className);
Constructor constructor = clazz.getConstructor();
MethodAccess access = MethodAccess.get(clazz);
Integer index = access.getIndex("run", double.class);
Object obj = constructor.newInstance();
long startTime = System.nanoTime();
for(int i = 0; i < 10000; i++) {
Object distance = access.invoke(obj, index, 2d*i);
}
long endTime = System.nanoTime();
System.out.println("调用耗时:" + (endTime - startTime));
}
}
优化后测试结果如下:


以上均基于JDK11版本之下的分析和测试,请根据实际版本参考。

3.7 结论
1.如果反射调用场景很少,则不需要太过纠结,直接反射调用就行了。

2.如果对性能要求较高,且无法缓存Method对象的情况下,尽量选择AsmReflect来进行反射调用。如果可以缓存,则也可以考虑使用使用Java 版 MethodAccessor,与AsmReflect差异并不是太大。 

联系我们

联系电话

4000-640-466

联系邮箱

service@f-li.cn

办公地址

上海黄浦区外滩源1号

谢谢,您的信息已成功发送。
请填写信息。