Java反射源码学习之旅 | 京东云技术团队

news/2024/11/8 15:12:13/

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、通过对入参的比较筛选出符合条件的Constructorfor (Constructor<T> constructor : constructors) {if (arrayContentsEq(parameterTypes,constructor.getParameterTypes())) {
//3、创建副本Constructorreturn 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

作者:京东物流 秦曌怡

来源:京东云开发者社区


http://www.ppmy.cn/news/657683.html

相关文章

陨石的秘密

设 f i , a , b , c f_{i,a,b,c} fi,a,b,c​表示当前深度小于等于 i i i并且有 a a a对 { } \{~\} { }, b b b对 [ ] [~] [ ]&#xff0c; c c c对 ( ) (~) ( )的方案数&#xff0c;根据优先级&#xff0c;将 f i , a , b , c f_{i,a,b,c} fi,a,b,c​分成左串&#xff0c;以及右…

python流星雨陨石代码

import random import pygame# 初始化 Pygame pygame.init()# 设置屏幕大小 screen pygame.display.set_mode((800, 600))# 设置标题 pygame.display.set_caption("陨石雨")# 设置背景颜色 background (0, 0, 0)# 设置陨石颜色 meteor_color (255, 255, 255)# 设置…

稀有金属陨石

陨铁,稀有金属陨石Rare metal meteorite,主要成份为铁、镍、锰、砷、钼、锆、铌、钌、铑、银、镉、铟、锡、锑过度金属的陨石。主要由铁纹石和镍纹石两种矿物组成,其次含有少量的石墨、陨磷铁镍矿、陨硫铬矿、陨碳铁、铬铁矿和陨硫铁等。在化学成分上除Ni和Fe外,还含有As、Mo、…

矿博会上互动活动每天都有奖 世界顶尖陨石受热捧

市民在矿博会现场观赏化石、参与活动。 “就是这个感觉&#xff0c;倍儿爽!”志愿者陈煦东高举着手机欣喜若狂地冲出去兑现他期待已久的四等奖——开紫水晶球&#xff0c;十分钟后他捧着一个沉甸甸的水晶球回来&#xff0c;“我运气真好&#xff0c;看&#xff0c;好多晶体。”…

Space Shooter之控制陨石随机旋转

思路&#xff1a; 如何让陨石随机旋转&#xff0c;这里陨石是3D模型。 在Update ()函数中调用GetComponent<Rigidbody>().transform.rotation Random.rotation;得到的效果是&#xff0c;陨石快速随机飞速的运动&#xff0c;这不是我们想要的。 我们可以在Start方法中调用…

最昂贵的石头?Tableau 带你探索陨石的历史

一颗“具有潜在危险性”的小行星——2011ES4 号小行星&#xff0c;于 9 月 2 日凌晨同地球擦肩而过——它距地球的最近距离不过地月距离的三分之一。在茫茫无际的宇宙中&#xff0c;这么近的距离算得上是“亲密接触”了。其实&#xff0c;每年奔向地球的小行星很多&#xff0c;…

YOLOX手把手实操:火星/月球陨石坑的数量统计

探索太空一直是人类乐此不疲的活动&#xff0c;随着科技的发展&#xff0c;人们对月球和火星愈发好奇。而在各种太空探索任务中&#xff0c;有效探测陨石坑具有至关重要的意义。 陨石坑是行星、卫星、小行星或其它天体表面通过陨石撞击而形成的环形凹坑。随着陨石颗粒大小撞击…

浏览器配置存在问题。。360断网急救箱未能修复

搞了半天&#xff0c;还以为ie中毒了~ 各种杀毒&#xff0c;各种查问题。。 最后是网卡坏掉了~ 换了一块新的网卡。。 好了。。