1 背景
前段时间组内针对“拷贝实例属性是应该用BeanUtils.copyProperties()还是MapStruct”这个问题进行了一次强烈的battle。反对MapStruct的同学给出了他厌弃BeanUtils的理由:因为用了反射,所以慢。
这个理由一下子拉回了我边远的记忆,在我刚开始理解反射这个Java个性的时候,简直看到的每一篇文章都会有“Java反射不能频繁应用”、“反射影响性能”之类的话语,过后只是当一个论断记下了这些话,却没有深究过为什么,所以正好借此机会来探索一下Java反射的代码。
2 反射包构造梳理
反射相干的代码次要在jdk rt.jar下的java.lang.reflect包下,还有一些相干类在其余包门路下,这里先按下不表。依照继承和实现的关系先简略划分下java.lang.reflect包:
① Constructor、Method、Field三个类型别离能够形容实例的构造方法、一般办法和字段。三种类型都间接或间接继承了AccessibleObject这个类型,此类型里次要定义两种办法,一种是通用的、对拜访权限进行解决的办法,第二种是可供继承重写的、与注解相干的办法。
② 只看选中的五种类型,咱们平时所用到的一般类型,譬如Integer、String,又或者是咱们自定义的类型,都能够用Class类型的实例来示意。Java引入泛型之后,在JDK1.5中裁减了其余四种类型,用于泛型的示意。别离是ParameterizedType(参数化类型)、WildcardType(通配符类型)、TypeVariable(类型变量)、GenericArrayType(泛型数组)。
③ 与②中形容的五种根本类型对应,下图这五个接口/类别离用来示意五种根本类型的注解相干数据。
④ 下图为实现动静代理的相干类与接口。java.lang.reflect.Proxy次要是利用反射的一些办法获取代理类的类对象,获取其构造方法,由此结构出一个实例。
java.lang.reflect.InvocationHandler是代理类须要实现的接口,由代理类实现接口内的invoke办法,此办法会负责代理流程和被代理流程的执行程序组织。
3 指标类实例的结构源码
以String类的对象实例化为例,看一下反射是如何进行对象实例化的。
Class<?> clz = Class.forName("java.lang.String");String s =(String)clz.newInstance();
Class对象的结构由native办法实现,以java.lang.String类为例,先看看结构好的Class对象都有哪些属性:
能够看到目前只有name一个属性有值,其余属性临时都是null或者默认值的状态。
下图是 clz.newInstance() 办法逻辑的流程图,接下来对其中次要的两个办法进行阐明:
从上图能够看出整个流程有两个外围局部。因为通常状况下,对象的结构都须要依附类里的构造方法来实现,所以第一局部就是拿到指标类对应的Constructor对象;第二局部就是利用Constructor对象,结构指标类的实例。
3.1 获取Constructor对象
首先上一张Constructor对象的属性图:
java.lang.Class#getConstructor0
此办法中次要做的工作是首先拿到指标类的Constructor实例数组(次要由native办法实现),数组里每一个对象都代表了指标类的一个构造方法。而后对数组进行遍历,依据办法入参提供的parameterTypes,找到合乎的Constructor对象,而后从新发明一个Constructor对象,属性值与原Constructor统一(称为正本Constructor),并且正本Constructor的属性 root 指向源Constructor,相当于对源Constructor对象进行了一层封装。
因为在getConstructor0()办法将返回值返回给调用方之后,调用方在后续的流程里进行了constructor.setAccesssible(true)的操作,这个办法的作用是敞开对constructor这个对象拜访时的Java语言拜访查看。语言拜访查看是个耗时的操作,所以正当猜想是为了进步反射性能敞开了这个查看,又出于平安思考,所以将最原始的对象进行了封装。
private Constructor<T> getConstructor0(Class<?>[] parameterTypes, int which) throws NoSuchMethodException{//1、拿到Constructor实例数组并进行筛选 Constructor<T>[] constructors = privateGetDeclaredConstructors((which == Member.PUBLIC)); //2、通过对入参的比拟筛选出符合条件的Constructor for (Constructor<T> constructor : constructors) { if (arrayContentsEq(parameterTypes, constructor.getParameterTypes())) {//3、创立正本Constructor return getReflectionFactory().copyConstructor(constructor); } } throw new NoSuchMethodException(getName() + ".<init>" + argumentTypesToString(parameterTypes));}
3.2 指标类实例的结构
sun.reflect.ConstructorAccessor#newInstance
此办法次要是利用上一步创立进去的Constructor对象,进行指标类实例的结构。Java为了进步反射的性能,为类实例的结构提供了两种计划,一种是虚拟机本人实现的native办法,一种是JDK包里的Java办法。
首先来看代码里对ConstructorAccessor对象的结构,通过代码能够看出在办法newConstructorAccessor中结构了ConstructorAccessor接口的两个实现类,两个对象进行了互相援用,像这样子:
//结构ConstructorAccessor对象public ConstructorAccessor newConstructorAccessor(Constructor<?> var1) { if (Modifier.isAbstract(var2.getModifiers())) { ...... } else { NativeConstructorAccessorImpl var3 = new NativeConstructorAccessorImpl(var1); DelegatingConstructorAccessorImpl var4 = new DelegatingConstructorAccessorImpl(var3); var3.setParent(var4); return var4; } }
在调用DelegatingConstructorAccessorImpl的newInstance办法时,相当于为NativeConstructorAccessorImpl做了一层代理,理论调用的是NativeConstructorAccessorImpl类实现的办法。
public Object newInstance(Object[] var1) throws InstantiationException, IllegalArgumentException, InvocationTargetException { return this.delegate.newInstance(var1); }
newInstance办法中决定应用哪种办法的是一个名为numInvocations的int类型的变量,每次调用到newInstance办法时,这个变量都会+1,当变量值超过阈值(15)时,就会应用Java形式进行指标类实例的发明,反之就会应用虚拟机实现的形式进行指标类实例的发明。
这样做是因为Java版本的实现流程很长,其中还蕴含了字节码结构的流程,所以首次结构比拟耗时,然而短暂来说性能更好,而native版本是初期应用速度较块,调用频繁的话性能会有所降落,所以做了依据阈值来判断应用哪个版本的设计。
public Object newInstance(Object[] var1) throws InstantiationException, IllegalArgumentException, InvocationTargetException { if (++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.c.getDeclaringClass())) {//Java办法结构对象 ConstructorAccessorImpl var2 = (ConstructorAccessorImpl)(new MethodAccessorGenerator()).generateConstructor(this.c.getDeclaringClass(), this.c.getParameterTypes(), this.c.getExceptionTypes(), this.c.getModifiers()); this.parent.setDelegate(var2); }//native办法实现实例化 return newInstance0(this.c, var1); }
重点关注以下Java版本的实现流程,首先结构了一个ConstructorAccessorImpl类的对象。这个对象的结构次要是依附在代码里依照字节码文件的格局结构进去一个字节数组实现的。首先创立了一个ByteVactor接口的实现类对象,此类有两个属性,一个字节数组,一个int类型的数用来标识地位。ClassFileAssembler类次要负责把各类值转化成字节码的格局而后填充到ByteVactor的实现类对象里。最初由ClassDefiner.defineClass办法对字节码数组进行解决,结构出ConstructorAccessorImpl对象。 最初ConstructorAccessorImpl实例还是会被传给newInstance0()这个native办法,以此来结构最终的指标类实例
private MagicAccessorImpl generate(final Class<?> var1, String var2, Class<?>[] var3, Class<?> var4, Class<?>[] var5, int var6, boolean var7, boolean var8, Class<?> var9) { //创立ByteVectorImpl对象 ByteVector var10 = ByteVectorFactory.create(); //创立ClassFileAssembler对象 this.asm = new ClassFileAssembler(var10);...... var10.trim();//拿出结构好的字节数组(就是字节码文件的格局) final byte[] var17 = var10.getData(); return (MagicAccessorImpl)AccessController.doPrivileged(new PrivilegedAction<MagicAccessorImpl>() { public MagicAccessorImpl run() { try {//调用native办法,创立ConstructorAccessorImpl类的实例//最初ConstructorAccessorImpl实例还是会被传给newInstance0()这个native办法,以此来结构最终的指标类实例 return (MagicAccessorImpl)ClassDefiner.defineClass(var13, var17, 0, var17.length, var1.getClassLoader()).newInstance(); } catch (IllegalAccessException | InstantiationException var2) { throw new InternalError(var2); } } }); }}
4 小结
最初根据上述学习思考下Java反射到底慢不慢这个问题。首先能够看到JDK为“反射时创建对象的过程”提供了两套实现,native版本更快然而也使得JVM无奈对其进行一些优化(譬如JIT的办法内联),当办法成为热点时,转用Java版本来进行实现则优化了这个问题。但Java版本的实现过程中须要动静生成字节码,还要加载一些额定的类,造成了内存的耗费,所以应用反射的时候还是该当留神一些是否会因为应用过多而造成内存溢出。
一次不成熟的源码学习历程,如有谬误还请斧正。
参考资料:
https://rednaxelafx.iteye.com/blog/548536
作者:京东物流 秦曌怡
起源:京东云开发者社区