Java通过反射调用方法源码分析

1. 前言

为了分析 Java 反射导致 Metaspace OOM 的问题,对通过反射调用方法的源码进行了分析

2. 相关源码地址

https://github.com/openjdk/jdk/blob/jdk8-b120/jdk/src/share/classes/java/lang/reflect/Method.java
https://github.com/openjdk/jdk/blob/jdk8-b120/jdk/src/share/classes/java/lang/reflect/Constructor.java
https://github.com/openjdk/jdk/blob/jdk8-b120/jdk/src/share/classes/sun/reflect/ReflectionFactory.java
https://github.com/openjdk/jdk/blob/jdk8-b120/jdk/src/share/classes/sun/reflect/misc/ReflectUtil.java
https://github.com/openjdk/jdk/blob/jdk8-b120/jdk/src/share/classes/sun/reflect/NativeMethodAccessorImpl.java
https://github.com/openjdk/jdk/blob/jdk8-b120/jdk/src/share/classes/sun/reflect/DelegatingMethodAccessorImpl.java
https://github.com/openjdk/jdk/blob/jdk8-b120/jdk/src/share/classes/sun/reflect/MethodAccessorGenerator.java
https://github.com/openjdk/jdk/blob/jdk8-b120/jdk/src/share/native/sun/reflect/NativeAccessors.c
https://github.com/openjdk/jdk/blob/jdk8-b120/hotspot/src/share/vm/prims/jvm.cpp

ReflectionFactory 类的源码中有有关于 Java 反射膨胀(Inflation)机制的说明

3. 反射调用非构造函数方法代码分析

3.1. Method.invoke

通过反射调用非构造函数方法时,需要执行 java.lang.reflect.Method invoke 方法,代码如下

    public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, obj, modifiers);
            }
        }
        MethodAccessor ma = methodAccessor;             // read volatile
        if (ma == null) {
            ma = acquireMethodAccessor();
        }
        return ma.invoke(obj, args);
    }

将变量 MethodAccessor ma 赋值为字段 methodAccessor,若为 null,则调用 acquireMethodAccessor 方法,并将返回值保存到变量 ma

最后调用变量 ma 的 invoke 方法,并返回对应返回值

若字段 methodAccessor 为 null,则最后调用的是 acquireMethodAccessor 方法返回值的 invoke 方法

若字段 methodAccessor 非 null,则最后调用的是 methodAccessor 字段的 invoke 方法

3.1.1. Method.acquireMethodAccessor

以上方法会调用 java.lang.reflect.Method acquireMethodAccessor 方法,代码如下

    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;
    }

acquireMethodAccessor 方法注释有说明,该方法没有进行并发控制,对一个方法生成多个 MethodAccessor 是正确的,避免进行同步控制可能会使实现更具有可扩展性。

root 字段只有在 java.lang.reflect.Method copy 方法中会被赋值为 this,其他情况下都是 null,因此需要调用 sun.reflect.ReflectionFactory newMethodAccessor 方法

sun.reflect.ReflectionFactory newMethodAccessor 方法的返回值会通过 setMethodAccessor 方法赋值到 methodAccessor 字段中,再返回该值

3.2. ReflectionFactory.newMethodAccessor

java.lang.reflect.Method acquireMethodAccessor 方法会调用 sun.reflect.ReflectionFactory newMethodAccessor 方法,newMethodAccessor 方法代码如下:

    public MethodAccessor newMethodAccessor(Method method) {
        checkInitted();

        if (noInflation && !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {
            return new MethodAccessorGenerator().
                generateMethod(method.getDeclaringClass(),
                               method.getName(),
                               method.getParameterTypes(),
                               method.getReturnType(),
                               method.getExceptionTypes(),
                               method.getModifiers());
        } else {
            NativeMethodAccessorImpl acc =
                new NativeMethodAccessorImpl(method);
            DelegatingMethodAccessorImpl res =
                new DelegatingMethodAccessorImpl(acc);
            acc.setParent(res);
            return res;
        }
    }

若 noInflation 字段为 true,且 ReflectUtil.isVMAnonymousClass 方法返回 false,会调用 sun.reflect.MethodAccessorGenerator generateMethod 方法,为通过反射调用的方法生成类

若不满足以上条件,则分别创建 NativeMethodAccessorImpl、DelegatingMethodAccessorImpl 对象,返回 DelegatingMethodAccessorImpl 对象

若 JVM 参数 sun.reflect.noInflation 设置为 true,通过反射调用某方法时,第一次就会为对应方法生成类

3.3. ReflectUtil.isVMAnonymousClass

ReflectUtil.isVMAnonymousClass 方法用于判断指定的类是否为 VM 匿名类(与 Java 匿名类不同),在指定的类名中包含"/"时会返回 true

使用“jmap -histo {pid}”命令查看某个 Java 进程当前的类与实例信息,可以看到类名包含"/"的类,即 VM 匿名类,如下所示:

 num     #instances         #bytes  class name
----------------------------------------------
 128:          1118          35776  org.springframework.util.ConcurrentReferenceHashMap$Segment$$Lambda$75/1682514668
 172:          1022          24528  org.reflections.scanners.Scanner$$Lambda$262/944164860
 173:          1019          24456  org.reflections.util.QueryBuilder$$Lambda$311/1967581772
 207:          1023          16368  java.util.stream.Collectors$$Lambda$280/2053777753
 209:          1022          16352  org.reflections.Reflections$$Lambda$261/414922522
 210:          1022          16352  org.reflections.scanners.Scanners$$Lambda$263/723106091

普通的类名中不会包含"/",不属于 VM 匿名类,因此以上 sun.reflect.ReflectionFactory newMethodAccessor 方法中是否会调用 sun.reflect.MethodAccessorGenerator generateMethod 方法为通过反射调用的方法生成类,取决于 noInflation 字段值

3.4. ReflectionFactory.checkInitted

在 sun.reflect.ReflectionFactory checkInitted 方法中

读取 JVM 参数 sun.reflect.noInflation,若非空且值为"true",则将 sun.reflect.ReflectionFactory 类的字段 noInflation 赋值为 true,该字段默认值为 false

读取 JVM 参数 sun.reflect.inflationThreshold,若非空且为合法的整形数值,则将 sun.reflect.ReflectionFactory 类的字段 inflationThreshold 赋值为对应值,该字段默认值为 15

3.5. MethodAccessorGenerator.generateMethod

在 sun.reflect.MethodAccessorGenerator generateMethod 方法中,会调用 generate 方法

在 sun.reflect.MethodAccessorGenerator generate 方法中,会动态生成类的字节码,使用了相同的结构,会执行通过反射调用的方法,因此反射膨胀机制为不同的方法生成类时,生成的类大小应该很接近,每个类占用 Metaspace 大小也应该接近

generate 方法中调用 generateName 获得生成类的类名,generateName 方法代码如下

    private static synchronized String generateName(boolean isConstructor,
                                                    boolean forSerialization)
    {
        if (isConstructor) {
            if (forSerialization) {
                int num = ++serializationConstructorSymnum;
                return "sun/reflect/GeneratedSerializationConstructorAccessor" + num;
            } else {
                int num = ++constructorSymnum;
                return "sun/reflect/GeneratedConstructorAccessor" + num;
            }
        } else {
            int num = ++methodSymnum;
            return "sun/reflect/GeneratedMethodAccessor" + num;
        }
    }

可以看到为通过反射调用的方法生成的 sun.reflect.GeneratedSerializationConstructorAccessor…、sun.reflect.GeneratedConstructorAccessor…、sun.reflect.GeneratedMethodAccessor… 类的类名前缀

3.6. DelegatingMethodAccessorImpl

sun.reflect.DelegatingMethodAccessorImpl 类的代码如下

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;
    }
}

在构造函数中,会调用 setDelegate 方法,参数 1 使用构造函数的参数 1

在 setDelegate 方法中,会将 delegate 字段赋值为参数 1

在 invoke 方法中,会调用 delegate 字段的 invoke 方法

即调用 DelegatingMethodAccessorImpl 构造函数时传入的对象,会在 invoke 方法中调用该对象的 invoke 方法

3.7. NativeMethodAccessorImpl

sun.reflect.NativeMethodAccessorImpl 类的代码如下

class NativeMethodAccessorImpl extends MethodAccessorImpl {
    private Method method;
    private DelegatingMethodAccessorImpl parent;
    private int numInvocations;

    NativeMethodAccessorImpl(Method method) {
        this.method = method;
    }

    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())) {
            MethodAccessorImpl acc = (MethodAccessorImpl)
                new MethodAccessorGenerator().
                    generateMethod(method.getDeclaringClass(),
                                   method.getName(),
                                   method.getParameterTypes(),
                                   method.getReturnType(),
                                   method.getExceptionTypes(),
                                   method.getModifiers());
            parent.setDelegate(acc);
        }

        return invoke0(method, obj, args);
    }

    void setParent(DelegatingMethodAccessorImpl parent) {
        this.parent = parent;
    }

    private static native Object invoke0(Method m, Object obj, Object[] args);
}

3.7.1. 构造函数

在构造函数中,会将 method 字段赋值为参数 1

3.7.2. invoke 方法

在 invoke 方法中,若满足 ++numInvocations > ReflectionFactory.inflationThreshold() 且 !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass()),会执行以下处理:

  • 调用 sun.reflect.MethodAccessorGenerator generateMethod 方法为当前通过反射调用的方法生成类
  • 调用 parent.setDelegate,即 sun.reflect.DelegatingMethodAccessorImpl setDelegate 方法,将 DelegatingMethodAccessorImpl 的 delegate 字段赋值为刚生成的类
  • 调用 native 方法 invoke0

numInvocations 代表当前方法通过反射调用的次数,ReflectionFactory.inflationThreshold() 方法返回 ReflectionFactory 类的 inflationThreshold 字段,由 JVM 参数 sun.reflect.inflationThreshold 决定

ReflectUtil.isVMAnonymousClass 方法已分析过,普通类不会满足该方法

3.7.3. 总结

当前方法通过反射调用次数大于 JVM 参数 sun.reflect.inflationThreshold 值时,会为对应的方法生成类,并将该类赋值到对应的 DelegatingMethodAccessorImpl 类的 delegate 字段;即后续都会调用生成的类的方法

3.8. NativeAccessors.c

在 NativeAccessors.c 中定义了 Java 中 sun.reflect.NativeMethodAccessorImpl 类的 native 方法 invoke0,会调用 JVM_InvokeMethod 方法:

JNIEXPORT jobject JNICALL Java_sun_reflect_NativeMethodAccessorImpl_invoke0
(JNIEnv *env, jclass unused, jobject m, jobject obj, jobjectArray args)
{
    return JVM_InvokeMethod(env, m, obj, args);
}

3.9. jvm.h

JVM_InvokeMethod 方法定义在 jvm.h 中

/*
 * java.lang.reflect.Method
 */
JNIEXPORT jobject JNICALL
JVM_InvokeMethod(JNIEnv *env, jobject method, jobject obj, jobjectArray args0);

3.10. jvm.cpp

JVM_InvokeMethod 方法在 jvm.cpp 中实现,会执行指定的 method,代码略

4. 反射调用构造函数代码分析

反射调用构造函数,与调用非构造函数的过程类似,具体步骤不再分析,以下为相对应的方法

反射调用构造函数时执行的类或方法反射调用非构造函数方法时执行的类或方法
java.lang.reflect.Constructor newInstancejava.lang.reflect.Method invoke
java.lang.reflect.Constructor acquireConstructorAccessorjava.lang.reflect.Method acquireMethodAccessor
sun.reflect.ReflectionFactory newConstructorAccessorsun.reflect.ReflectionFactory newMethodAccessor
sun.reflect.MethodAccessorGenerator generateConstructorsun.reflect.MethodAccessorGenerator generateMethod
sun.reflect.NativeConstructorAccessorImplsun.reflect.NativeMethodAccessorImpl
sun.reflect.DelegatingConstructorAccessorImplsun.reflect.DelegatingMethodAccessorImpl
sun.reflect.NativeConstructorAccessorImpl newInstancesun.reflect.NativeMethodAccessorImpl invoke
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值