JAVA基础:反射

本文详细介绍了JAVA反射机制,包括如何加载类、创建对象、获取对象域及调用方法。重点探讨了method的get和invoke方法,以及反射生成的GeneratedMethodAccessorXXX类的原理和线程安全问题。文章指出,反射在启动时性能较优,但长时间运行后可能引发线程安全问题和内存占用过多导致的OOM风险,建议理解其内部机制并谨慎使用。
摘要由CSDN通过智能技术生成

什么是反射?

反射可以在运行时动态加载类,创建对象。这种类和对象在编译期间都是不确定的,只有在运行到对应的反射代码才能确定下来。反射还可以获取对象自身的信息,包括属性、方法等,并且可以打破访问修饰符的限制,设置并访问私有的属性,获取并调用私有的方法。

通过反射加载类

反射,最刚开始接触其实是在jdbc的学习中。
看下面获取mysql连接的代码

//反射加载mysql驱动
Class.forName(“com.mysql.jdbc.Driver”);
String url = “jdbc:mysql://localhost:3306/test?user=root&password=123456″;
Connection con = DriverManager.getConnection(url);                                                            

Class.forName用来加载com.mysql.jdbc.Driver这个驱动类,并且返回Class对象,但是这里并没有去接收它的返回值,那么DriverMannger是如何得到这个驱动类的呢?
看下com.mysql.jdbc.Driver的实现就知道了

  static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }

可以看到mysql驱动类中有这一段静态块,在加载驱动类时,初始化的阶段会执行静态块里的代码,加载驱动类到DriverManager.

此外对于Class.forName需要了解一点就是,通过调用方的类加载器反射加载该类。

通过反射创建对象

 Class clazz =  Class.forName("com.domain.Person");
 //创建对象,注意必须要有空构造器
       Object obj =  clazz.newInstance();
//两种方法判断某个对象时某一个类的对象
        System.out.println(obj instanceof Person);
        System.out.println(clazz.isInstance(obj));

通过反射获取对象的域

 	 Class clazz =  Class.forName("com.domain.Person");
       Object obj =  clazz.newInstance();
       //可获取到私有域
       //getField只可获得公开域
        Field field = clazz.getDeclaredField("name");

//私有域 可以设置为可以访问,打破访问修饰符的访问控制权限
        field.setAccessible(true);
        //为obj的name属性设置值 yang
        field.set(obj,"yang");
        //获取输出
        System.out.println(field.get(obj));

//静态域
 Field field1 = clazz.getField("type");
       field1.set(clazz,"人");
        System.out.println(field1.get(clazz));

通过反射调用对象的方法

//获取方法,可以获取到私有方法    
    Method method = clazz.getDeclaredMethod("show",int.class);
    //   私有方法设置可访问,打破访问修饰符的访问权限控制
    //        method.setAccessible(true);

//执行方法
        method.invoke(obj,1);
//静态方法
 Method method1 = clazz.getDeclaredMethod("say",String.class);
        method1.invoke(clazz,"hello");

method深入

1、method的get方法

//这个方法读取缓存找那个的 method对象,通过public only来过滤私有和公开方法
 private Method[] privateGetDeclaredMethods(boolean publicOnly) {
        checkInitted();
        Method[] res;
        ReflectionData<T> rd = reflectionData();
        if (rd != null) {
            res = publicOnly ? rd.declaredPublicMethods : rd.declaredMethods;
            if (res != null) return res;
        }
        // No cached value available; request value from VM
        res = Reflection.filterMethods(this, getDeclaredMethods0(publicOnly));
        if (rd != null) {
            if (publicOnly) {
                rd.declaredPublicMethods = res;
            } else {
                rd.declaredMethods = res;
            }
        }
        return res;
    }


        SoftReference<ReflectionData<T>> reflectionData = this.reflectionData;

reflectionData是软引用对象,在内存不足时会被回收,如果被回收了在需要用到他,又需要从jvm中取一把。

把从缓存中取出并过滤好的方法数组拿出来,按要求的方法签名去找对应方法

 private static Method searchMethods(Method[] methods,
                                        String name,
                                        Class<?>[] parameterTypes)
    {
        Method res = null;
        String internedName = name.intern();
        for (int i = 0; i < methods.length; i++) {
            Method m = methods[i];
            if (m.getName() == internedName
                && arrayContentsEq(parameterTypes, m.getParameterTypes())
                && (res == null
                    || res.getReturnType().isAssignableFrom(m.getReturnType())))
                res = m;
        }

//注意下面这个判断,不要看反了,
//这里是说,如果没有找到这个方法,直接返回出去null。否则就copy这个方法返回出去,
//所以由此可知,每次取method,得到的都不是同一个对象,所以能复用method就尽量复用
        return (res == null ? res : getReflectionFactory().copyMethod(res));
    }

ok,看到这里,我试了一下,取两次method,然后输出hashCode一看,,居然一样,不信邪,又来几次,还是一样,一度怀疑人生。
后来仔细一看才发现method重写了hashCode,它的hashCode跟他的签名有关,跟地址无关。
所以,判断两个对象是否是同一个对象,坚决不能用hashCode。
要用==,
没错用==就明显对了。

2、method的invoke方法
invoke最终调用到MethodAccessor 的invoke

public interface MethodAccessor {
    Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException;
}

method里有个root对象,也就是缓存中的method,也就是这个method是通过root复制出来的,如果root里有MethodAccessor ,就直接使用,否则就需要创建。
MethodAccessor 有三个实现类:
DelegatingMethodAccessorImpl
NativeMethodAccessorImpl
GeneratedMethodAccessorXXX

其中DelegatingMethodAccessorImpl是最终注入到method的MethodAccessor中的,也就是最终要调用到DelegatingMethodAccessorImpl的invoke,DelegatingMethodAccessorImpl作为一个代理类存在,委托执行NativeMethodAccessorImpl 或者 GeneratedMethodAccessorXXX【反射生成】的invoke

class DelegatingMethodAccessorImpl extends MethodAccessorImpl {
    private MethodAccessorImpl delegate;

    DelegatingMethodAccessorImpl(MethodAccessorImpl var1) {
        this.setDelegate(var1);
    }

    public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
        return this.delegate.invoke(var1, var2);
    }

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

ok,现在的核心在NativeMethodAccessorImpl

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

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

    public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
      
      //在15次之后,设置对应的代理对象为GeneratedMethodAccessorXXX,
      //这个反射得到的类中invoke,其实就是普通的方法调用
        if (++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) {
            MethodAccessorImpl var3 = (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers());
            this.parent.setDelegate(var3);
        }

//在方法执行的前15次默认都是调用原生的native方法
        return invoke0(this.method, var1, var2);
    }

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

    private static native Object invoke0(Method var0, Object var1, Object[] var2);
}

其中我上面说的是15次就是ReflectionFactory.inflationThreshold()这个方法返回的,这个15当然也不是一尘不变的,我们可以通过-Dsun.reflect.inflationThreshold=xxx来指定,我们还可以通过-Dsun.reflect.noInflation=true来直接绕过上面的15次NativeMethodAccessorImpl调用,和-Dsun.reflect.inflationThreshold=0的效果一样的。

为什么要有两种方式去执行方法的调用?

1、原生的在启动时性能较好,时间长了,性能就会变差
2、反射生成类的在启动时性能不好,因为需要去反射生成类,启动一段时间后,性能就很好了。
所以两者进行了结合,先使用原生,一段时间后,使用反射生成类。

并发调用method产生线程安全问题?

在调用15次后,生成反射类来实现调用的方法并没有加锁,也就是并不是线程安全的。假设在外部method的调用层也没有进行锁的控制,那么就会出现一些问题。
假设1000个线程同时进来,反射类,那么就会生成1000个类,虽然最后留下来的就一个类,而且程序也都能正常秩序,但是会有999个类在未被卸载之前会占用内存,在极端情况下就会OOM。

GeneratedMethodAccessorXXX如何卸载?

首先明确两点:
1、GeneratedMethodAccessorXXX 需要被卸载,为什么?
假设GeneratedMethodAccessorXXX 都不被卸载,当程序中大量使用反射调用对象时,很可能因为GeneratedMethodAccessorXXX 的大量生产而导致OOM
2、JVM虚拟机自带的类加载器能不能卸载类?
在JVM运行期间,JVM会持有默认的类加载器,默认的类加载器又会持有他们加载的Class对象,所以这些Class对象都是可以触及的,所以在运行期间不可能卸载类。

那么如何实现GeneratedMethodAccessorXXX 的卸载,最简单的方式就是自定义类加载器去实现GeneratedMethodAccessorXXX 的加载,因为用户自定义的类加载器所加载的类可以被卸载。
比如JSP类加载器,每个JSP对应一个类加载器,当发现有JSP修改,就会卸载原来的类加载器和类,重新创建类加载器,加载修改后的jsp所编译的字节码文件。

 static Class<?> defineClass(String var0, byte[] var1, int var2, int var3, final ClassLoader var4) {
        ClassLoader var5 = (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
            public ClassLoader run() {
            //可以看到GeneratedMethodAccessorXXX  确实使用自定义的类加载器  DelegatingClassLoader
                          return new DelegatingClassLoader(var4);
            }
        });
        return unsafe.defineClass(var0, var1, var2, var3, var5, (ProtectionDomain)null);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值