反射性能分析和代理原理

反射性能分析和代理原理

反射为什么效率差?

一、结论

  1. Method#invoke 方法会对参数做装箱和拆箱操作
  2. 需要检查方法可见性
  3. 需要校验参数
  4. 反射方法难以内联
  5. JIT 无法优化

二、源码分析

1. 使用案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class RefTest {
public static void main(String[] args) {
try {
//1. 创建 Class 对象
Class clazz = Class.forName("com.zy.java.RefTest");
Object refTest = clazz.newInstance();

//2. 获取 Method 对象
Method method = clazz.getDeclaredMethod("refMethod");

//3. 调用 invoke 方法
method.invoke(refTest);
} catch (Exception e) {
e.printStackTrace();
}
}

public void refMethod() {
}
}

下面从 获取 Method 对象 开始分析,方法getMethod/ getDeclaredMethod都可以用于获取 Method 对象

2. getMethod 和 getDeclaredMethod 有什么区别?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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);
}
}

获取方法的流程分三步走:

  1. 检查方法权限

为什么getMethodcheckMemberAccess传入的是 Member.PUBLIC,而 getDeclaredMethod传入的是Member.DECLARED这两个值有什么区别呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface Member {
/**
* Identifies the set of all public members of a class or interface,
* including inherited members.
*
* 翻译:PUBLIC 会包括所有的 public 方法,包括父类的方法
*/
public static final int PUBLIC = 0;

/**
* Identifies the set of declared members of a class or interface.
* Inherited members are not included.
*
* 翻译:DECLARED 会包括所有自己定义的方法,public,protected,private 都在此,但是不包括父类的方法
*/
public static final int DECLARED = 1;
}

这也是getMethodgetDeclaredMethod的区别。

  1. 获取方法 Method 对象

    getMethod 中获取方法调用的是 getMethod0,而 getDeclaredMethod 获取方法调用的是

    privateGetDeclaredMethods,实际上而 getMethod0 会递归查找父类的方法,最终会调用到

    privateGetDeclaredMethods 方法。

1
2
3
4
5
6
/**
* publicOnly:是否只获取公共方法
*/
private Method[] privateGetDeclaredMethods(boolean publicOnly) {
//...
}
  1. 返回方法的拷贝

    为什么是返回拷贝?

3. getMethod 分析

流程一:检查方法权限做了什么事情?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Class {
private void checkMemberAccess(SecurityManager sm, int which,
Class<?> caller, boolean checkProxyInterfaces) {
/*
* 主要是为了检查是否可以访问对象成员。
*/
final ClassLoader ccl = ClassLoader.getClassLoader(caller);
if (which != Member.PUBLIC) {
final ClassLoader cl = getClassLoader0();
if (ccl != cl) {
sm.checkPermission(SecurityConstants.CHECK_MEMBER_ACCESS_PERMISSION);
}
}
this.checkPackageAccess(sm, ccl, checkProxyInterfaces);
}
}

流程二:如何获取 Method 对象?

先看 getMethod0 :

1
2
3
4
5
6
7
8
9
10
11
12
13
class Class {
private Method getMethod0(String name, Class<?>[] parameterTypes) {

//1. 获取到 MethodList 对象
PublicMethods.MethodList res = getMethodsRecursive(
name,
parameterTypes == null ? EMPTY_CLASS_ARRAY : parameterTypes,
/* includeStatic */ true);

//2. 筛选返回值类型最为具体的方法
return res == null ? null : res.getMostSpecific();
}
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Class {
private PublicMethods.MethodList getMethodsRecursive(String name,
Class<?>[] parameterTypes,
boolean includeStatic) {
// 1. 获取自己的 public 方法,这里调用了 privateGetDeclaredMethods 方法
Method[] methods = privateGetDeclaredMethods(/* publicOnly */ true);

// 2. 筛选符合条件的方法,构造 MethodList 对象
PublicMethods.MethodList res = PublicMethods.MethodList
.filter(methods, name, parameterTypes, includeStatic);
// 找到方法,直接返回
if (res != null) {
return res;
}

// 3. 没有找到方法,就获取其父类,递归调用 getMethodsRecursive 方法
Class<?> sc = getSuperclass();
if (sc != null) {
res = sc.getMethodsRecursive(name, parameterTypes, includeStatic);
}

// 4. 获取接口中对应的方法
for (Class<?> intf : getInterfaces(/* cloneArray */ false)) {
res = PublicMethods.MethodList.merge(
res, intf.getMethodsRecursive(name, parameterTypes,
/* includeStatic */ false));
}

return res;
}
}

为什么通过名字和参数拿到的是一个Method列表?

编写 Java 代码时,同一个类是不能有方法名和方法参数都相同的方法

而实际上,在 JVM 中,一个方法签名是和 返回值、方法名、方法参数 三者相关的。

也就是说,在 JVM 中,可以存在 方法名和方法参数都相同,但是返回值不同的方法。

所以这里返回的是一个方法链表。

由 getMethodsRecursive 方法可知,最终还是会走到 privateGetDeclaredMethods 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Class {
private Method[] privateGetDeclaredMethods(boolean publicOnly) {
Method[] res;
// 1. 通过缓存获取 ReflectionData
ReflectionData<T> rd = reflectionData();
if (rd != null) {
res = publicOnly ? rd.declaredPublicMethods : rd.declaredMethods;
if (res != null) return res;
}

// 2. ReflectionData中没有获取到方法列表,通过 JVM 的 getDeclaredMethods0 是一个本地方法
res = Reflection.filterMethods(this, getDeclaredMethods0(publicOnly));
if (rd != null) {
if (publicOnly) {
rd.declaredPublicMethods = res;
} else {
rd.declaredMethods = res;
}
}
return res;
}
}

先看看 relectionData 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Class {
private ReflectionData<T> reflectionData() {
//1. 获取可能存在的 ReflectionData 的软引用对象
SoftReference<ReflectionData<T>> reflectionData = this.reflectionData;
int classRedefinedCount = this.classRedefinedCount;

//2. 如果软引用有值,直接返回
ReflectionData<T> rd;
if (reflectionData != null &&
(rd = reflectionData.get()) != null &&
rd.redefinedCount == classRedefinedCount) {
return rd;
}

//3. 如果没有 重新生成
return newReflectionData(reflectionData, classRedefinedCount);
}

//ReflectionData对象中保存了类的类型信息
private static class ReflectionData<T> {
volatile Field[] declaredFields;
volatile Field[] publicFields;
volatile Method[] declaredMethods;
volatile Method[] publicMethods;
volatile Constructor<T>[] declaredConstructors;
volatile Constructor<T>[] publicConstructors;
// Intermediate results for getFields and getMethods
volatile Field[] declaredPublicFields;
volatile Method[] declaredPublicMethods;
volatile Class<?>[] interfaces;

// Cached names
String simpleName;
String canonicalName;
static final String NULL_SENTINEL = new String();

// Value of classRedefinedCount when we created this ReflectionData instance
final int redefinedCount;
}
}

这就是 getMethod 方法的整个实现了。

再回过头看一下 getDeclaredMethod 方法的实现,通过 privateGetDeclaredMethods 获取方法以后,会通过 searchMethods 对方法进行筛选:

1
2
3
4
5
6
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
// ...
Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
// ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Class {
private static Method searchMethods(Method[] methods,
String name,
Class<?>[] parameterTypes)
{
ReflectionFactory fact = getReflectionFactory();
Method res = null;
for (Method m : methods) {
//1. 比较方法名
if (m.getName().equals(name)
//2. 比较方法参数
&& arrayContentsEq(parameterTypes,
fact.getExecutableSharedParameterTypes(m))
//3. 比较返回值 会选择返回最具体的那一个
&& (res == null
|| (res.getReturnType() != m.getReturnType()
&& res.getReturnType().isAssignableFrom(m.getReturnType()))))
res = m;
}
return res;
}
}

流程三:Method#copy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Method {
Method copy() {
// This routine enables sharing of MethodAccessor objects
// among Method objects which refer to the same underlying
// method in the VM. (All of this contortion is only necessary
// because of the "accessibility" bit in AccessibleObject,
// which implicitly requires that new java.lang.reflect
// objects be fabricated for each reflective call on Class
// objects.)
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
res.methodAccessor = methodAccessor;
return res;
}
}

每一次返回的Method对象其实都是一个新的对象,但是:

  1. 新对象的root属性都指向源Method对象
  2. methodAccessor是所有对象共享的

4. invoke 分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@CallerSensitive
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
/*
* 1. 权限检查
* 如果 override == true,就跳过检查
* 调用 Method#setAccessible(true),就是设置 override 值为 true
* 首次可能为空 因此去创建 MethodAccessor
*/
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}

/*
* 2. 获取 methodAccessor
* 这里的 methodAccessor 就是拷贝时使用的 MethodAccessor
*
*/
MethodAccessor ma = methodAccessor;
if (ma == null) {
ma = acquireMethodAccessor();
}

//3. 调用
return ma.invoke(obj, args);
}

首次调用 MethodAccessor 为空,因此进入 acquireMethodAccessor() 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private MethodAccessor acquireMethodAccessor() {

MethodAccessor tmp = null;
//1. 从root的MethodAccessor中获取
if (root != null) tmp = root.getMethodAccessor();
if (tmp != null) {
methodAccessor = tmp;
} else {
//2. 新生成 MethodAccessor
tmp = reflectionFactory.newMethodAccessor(this);
setMethodAccessor(tmp);
}

return tmp;
}

之后便会执行 MethodAccessor 的invoke,那么 MethodAccessor 到底做了什么呢?

简单来说,MethodAccessor 做了两件事:

  1. 解析参数,解封操作
  2. 调用对象的方法

那么这里有两类MethodAccessor :

  1. 默认 Native 版本的 MethodAccessor 实现——NativeMethodAccessorImpl
  2. Java 版本的 MethodAccessor 实现—— MethodAccessorImpl

为什么会有两类?

因为跨越native边界会对优化有阻碍作用,它就像个黑箱一样让虚拟机难以分析也将其内联,于是运行时间长了之后反而是java版本的代码更快些

Java 版本的 MethodAccessorImpl 调用效率比 Native 版本要快 20 倍以上,但是 Java 版本加载时要比 Native 多消耗 3-4 倍资源,所以默认会调用 Native 版本,如果调用次数超过 15 次以后,就会选择运行效率更高的 Java 版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//java版本的 MethodAccessor,相当于执行 A.foo("aaa") 
package sun.reflect;

public class GeneratedMethodAccessor1 extends MethodAccessorImpl {
public GeneratedMethodAccessor1() {
super();
}

public Object invoke(Object obj, Object[] args)
throws IllegalArgumentException, InvocationTargetException {
// 1. 拆箱,转换
if (obj == null) throw new NullPointerException();
try {
A target = (A) obj;
if (args.length != 1) throw new IllegalArgumentException();
String arg0 = (String) args[0];
} catch (ClassCastException e) {
throw new IllegalArgumentException(e.toString());
} catch (NullPointerException e) {
throw new IllegalArgumentException(e.toString());
}
//2. 调用
try {
target.foo(arg0);
} catch (Throwable t) {
throw new InvocationTargetException(t);
}
}
}

常用代理技术

JDK代理

jdk代理分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//代理类反编译
public final class $Proxy0 extends Proxy implements UserService {

private static Method m3;

public $Proxy0(InvocationHandler paramInvocationHandler) {
super(paramInvocationHandler);
}

public final void displayUser(String paramString) {
this.h.invoke(this, m3, new Object[]{paramString});
}

static {
m3 = Class.forName("com.demo.proxy.jdk.UserService").getMethod("displayUser", new Class[]{Class.forName("java.lang.String")});
}
}

Cglib代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//代理类反编译
public class PersonService$$EnhancerByCGLIB$$eaaaed75 extends PersonService implements Factory {
private boolean CGLIB$BOUND;
private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
private static final Callback[] CGLIB$STATIC_CALLBACKS;
private MethodInterceptor CGLIB$CALLBACK_0;//拦截器
private static final Method CGLIB$setPerson$0$Method;//被代理方法
private static final MethodProxy CGLIB$setPerson$0$Proxy;//代理方法
private static final Object[] CGLIB$emptyArgs;//方法参数

static void CGLIB$STATICHOOK1() {
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
Class localClass1 = Class.forName("com.demo.proxy.cglib.PersonService$$EnhancerByCGLIB$$eaaaed75");//代理类
Class localClass2;//被代理类PersionService

Method[] tmp223_220 = ReflectUtils.findMethods(new String[]{"setPerson", "()V"}, (localClass2 = Class.forName("com.demo.proxy.cglib.PersonService")).getDeclaredMethods());
CGLIB$setPerson$0$Method = tmp223_220[0];
CGLIB$setPerson$0$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "setPerson", "CGLIB$setPerson$0");
}

//methodProxy.invokeSuper会调用
final void CGLIB$setPerson$0() {
super.setPerson();
}

//methodProxy.invoke会调用,这就是为什么在拦截器中调用methodProxy.invoke会死循环,一直在调用拦截器
public final void setPerson() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 != null) {
//调用拦截器
var10000.intercept(this, CGLIB$setPerson$0$Method, CGLIB$emptyArgs, CGLIB$setPerson$0$Proxy);
} else {
super.setPerson();
}
}
}

Cglib代理分析

Method 和 MethodProxy 有什么区别?

MethodProxy 是 cglib 为了避免反射使用的一种代替方式,但是功能是一样的。

cglib与jdk代理对比?

  1. JDK动态代理是实现了被代理对象的接口,Cglib是继承了被代理对象。
  2. JDK和Cglib都是在运行期生成字节码,JDK是直接写Class字节码,Cglib使用ASM框架写Class字节码,Cglib代理实现更复杂,生成代理类比JDK效率低。
  3. JDK调用代理方法,是通过反射机制调用,Cglib是通过FastClass机制直接调用方法,Cglib执行效率更高。

本文标题:反射性能分析和代理原理

文章作者:Sun

发布时间:2020年05月28日 - 15:05

最后更新:2020年06月23日 - 15:06

原始链接:https://sunyi720.github.io/2020/05/28/Java/反射与代理/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。