Java反射及性能详细
作者:晟_1044290 发布时间:2023-08-20 20:49:42
我们今天不探讨框架层面的内容,暂且认为90%的框架不存在无法容忍的性能问题。在做系统调优的过程中,面对随处可见的invoke
调用,我的内心其实是比较抵触的,倒不是说反射怎么不好,对于优雅的源码来说,反射必不可少,个人抵触的原因主要是因为反射把真实的方法“隐藏”的很好,面对长长的线程栈比较头大而已。而且我心里一直有个大大的问号,反射到底存在哪些性能问题。
带着这个疑惑,基于java最基本的反射使用,通过查看资料及源码阅读,有如下的总结和分享,欢迎交流和指正。
一、准备
注:本案例针对JDK1.8
测试代码:
【TestRef.java】
public class TestRef {
public static void main(String[] args) {
try {
Class<?> clazz = Class.forName("com.allen.commons.entity.CommonTestEntity");
Object refTest = clazz.newInstance();
Method method = clazz.getMethod("defaultMethod");
//Method method1 = clazz.getDeclaredMethod("defaultMethod");
method.invoke(refTest);
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
---------------------------------------------------------------------------------------
【CommonTestEntity.java】
public class CommonTestEntity {
static {
System.out.println("CommonTestEntity执行类加载...");
}
public CommonTestEntity() {
System.out.println(this.getClass() + " | CommonTestEntity实例初始化 | " + this.getClass().getClassLoader());
}
public void defaultMethod() {
System.out.println("执行实例方法:defaultMethod");
}
}
二、反射调用流程
1.反射的使用
1)创建
class
对象(类加载,使用当前方法所在类的ClassLoader
来加载)2)获取
Method
对象(getMethod
和getDeclaredMethod
)3)调用
invoke
方法
2.getMethod 和 getDeclaredMethod区别
getMethod源码如下:
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 method;
}
---------------------------------------------------------------------------------------
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 method;
}
获取方法的流程分三步走:
a.检查方法权限
b.获取方法
Method
对象c.返回方法
主要有两个区别:
1.getMethod
中 checkMemberAccess
传入的是 Member.PUBLIC
,而 getDeclaredMethod
传入的是 Member.DECLARED
。
代码中的注释:
注释里解释了 PUBLIC
和 DECLARED
的不同,PUBLIC
会包括所有的 public
方法,包括父类的方法,而 DECLARED
会包括所有自己定义的方法,public
,protected
,private
都在此,但是不包括父类的方法。
2.getMethod
中获取方法调用的是 getMethod0
,而 getDeclaredMethod
获取方法调用的是 privateGetDeclaredMethods
。privateGetDeclaredMethods
是获取类自身定义的方法,参数是 boolean publicOnly
,表示是否只获取公共方法。
privateGetDeclaredMethods 源码如下:
// Returns an array of "root" methods. These Method objects must NOT
// be propagated to the outside world, but must instead be copied
// via ReflectionFactory.copyMethod.
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;
}
①relectionData
通过缓存获取
②如果缓存没有命中的话,通过 getDeclaredMethods0
获取方法
getMethod0源码如下:
private Method getMethod0(String name, Class<?>[] parameterTypes, boolean includeStaticMethods) {
MethodArray interfaceCandidates = new MethodArray(2);
Method res = privateGetMethodRecursive(name, parameterTypes, includeStaticMethods, interfaceCandidates);
if (res != null)
return res;
// Not found on class or superclass directly
interfaceCandidates.removeLessSpecifics();
return interfaceCandidates.getFirst(); // may be null
}
其中privateGetMethodRecursive
方法中也会调用到privateGetDeclaredMethods
方法和searchMethods
方法
3.getMethod
方法流程
4.getDeclaredMethod
方法流程
三、调用反射方法
invoke源码:
class Method {
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (!override) {
Class<?> caller = Reflection.getCallerClass();
// 1. 检查权限
checkAccess(caller, clazz,
Modifier.isStatic(modifiers) ? null : obj.getClass(),
modifiers);
}
// 2. 获取 MethodAccessor
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
// 创建 MethodAccessor
ma = acquireMethodAccessor();
}
// 3. 调用 MethodAccessor.invoke
return ma.invoke(obj, args);
}
}
Method.invoke()
实际上并不是自己实现的反射调用逻辑,而是委托给sun.reflect.MethodAccessor
来处理。
每个实际的Java方法只有一个对应的Method
对象作为root(实质上就是Method类的一个成员变量)。每次在通过反射获取Method
对象时新创建Method
对象把root
封装起来。在第一次调用一个实际Java方法对应得Method
对象的invoke()方法之前,实现调用逻辑的MethodAccessor
对象是第一次调用时才会新建并更新给root,然后调用MethodAccessor.invoke()
真正完成反射调用。
MethodAccessor
只是单方法接口,其invoke()
方法与Method.invoke()
的对应。创建MethodAccessor
实例的是ReflectionFactory
。
MethodAccessor
实现有两个版本,一个是Java实现的,另一个是native code
实现的。
Java 版本的 MethodAccessorImpl
调用效率比 Native
版本要快 20 倍以上,但是 Java 版本加载时要比 Native 多消耗 3-4 倍资源,所以默认会调用 Native
版本,如果调用次数超过 15 次以后,就会选择运行效率更高的 Java 版本。
Native版本中的阈值(静态常量)
四、反射效率低的原因
1.Method#invoke
方法会对参数做封装和解封操作
我们可以看到,
invoke
方法的参数是Object[]
类型,也就是说,如果方法参数是简单类型(8中基本数据类型)的话,需要在此转化成 Object 类型,例如 long ,在javac compile
的时候 用了Long.valueOf()
转型,也就大量了生成了Long
的 Object, 同时 传入的参数是Object[]数值,那还需要额外封装object数组。而在上面
MethodAccessorGenerator#emitInvoke
方法里我们看到,生成的字节码时,会把参数数组拆解开来,把参数恢复到没有被 Object[] 包装前的样子,同时还要对参数做校验,这里就涉及到了解封操作。因此,在反射调用的时候,因为封装和解封,产生了额外的不必要的内存浪费,当调用次数达到一定量的时候,还会导致 GC。
2.需要检查方法可见性
checkAccess
方法
3.需要遍历方法并校验参数
PrivateGetMethodRecursive
中的searhMethod
4.JIT 无法优化
在 JavaDoc 中提到:
Because reflection involves types that are dynamically resolved, certain Java virtual machine optimizations can not be performed. Consequently, reflective operations have slower performance than their non-reflective counterparts, and should be avoided in sections of code which are called frequently in performance-sensitive applications.
五、反射优化
1.(网上看到)尽量不要getMethods()
后再遍历筛选,而直接用getMethod(methodName)
来根据方法名获取方法
但是在源码中获取方法的时候,在searchMethods
方法中,其实也是采用遍历所有方法的方式。但是相比getMethod,getDeclaredMethod
遍历的方法数量相对较少,因为不包含父类的方法。
2.缓存class
对象
a)Class.forName
性能比较差
b)如上所述,在获取具体方法时,每次都要调用native
方法获取方法列表并遍历列表,判断入参类型和返回类型。将反射得到的method/field/constructor
对象做缓存,将极大的提高性能。
3.涉及 * 的:在实际使用中,CGLIB和Javassist
基于动态代码的代理实现,性能要优于JDK自带的 *
JDK自带的 * 是基于接口的 * ,相比较直接的反射操作,性能还是高很多,因为接口实例相关元数据在静态代码块中创建并且已经缓存在类成员属性中,在运行期间是直接调用,没有额外的反射开销。
4.使用ReflectASM
,通过生成字节码的方式加快反射(使用难度大)
来源:https://www.tuicool.com/articles/jUvAbqm


猜你喜欢
- Springboot 内置tomcat禁止不安全HTTP方法1、在tomcat的web.xml中可以配置如下内容让tomcat禁止不安全的H
- 序列化与反序列化序列化:把对象转换成字节的过程,称为对象序列化反序列化:把字节恢复成对象的过程,称为反序列化对象的持久化概念:把字节保存的硬
- 前言Java中共有八种基本数据类型:byte,int,short,long,float,double,char,boolean。计算机中的基
- Android自带的SeekBar是水平的,要垂直的,必须自己写一个类,继承SeekBar。一个简单的垂直SeekBar的例子:(但是它其实
- 前言这里介绍一个.net自身携带的类ImageAnimator,这个类类似于控制动画的时间轴,使用ImageAnimator.CanAnim
- 前言有时候我们开发时会发现有些方法调用非常多,但它的默认的调用方法却要传很多参数进去而且还得记得调用具体的写法,比如Toast,不止要调用m
- 本文实例为大家分享了Android实现五子棋游戏的具体代码,供大家参考,具体内容如下实现环境: android studio 3
- 1、在pom.xml文件引入依赖<!-- 运行状态监控actuator依赖 --> <depen
- 线程安全解决方案synchronized,ReentrantLock,Atomic 使用场景描述在实际开发过程中如果服务量,请求频繁,就会经
- 日志输出是所有系统必备的,很多开发人员可能因为常常使用log4j而忽视了JDK logging模块,两者之间是否有联系?是怎样的联系?JDK
- 一. 思路今天接到个小任务,让把json文件转换成excel文件,按照列展开.思路:既然json已经都已经是现成的,那直接将jso
- 本文研究的主要内容是Java编程二项分布的递归和非递归实现,具体如下。问题来源:算法第四版 第1.1节 习题27:return (1.0 -
- 摘要:最近有一个需求,为客户提供一些Restful API 接口,QA使用postman进行测试,但是postman的测试接口与java调用
- 本文实例为大家分享了java将一个目录下的所有文件复制n次的具体代码,供大家参考,具体内容如下1. 文件复制示意图 2.java程
- 本文实例讲述了C#实现去除Strings中空格的方法,分享给大家供大家参考。具体实现方法如下:一般来说,你或许知道你能使用String.Tr
- io学习框架:文件:保存数据的地方。1)常见文件对象的相关构造器和方法:当进行File file = new File(filePath);
- 本文实例讲述了C#简单读写txt文件的方法。分享给大家供大家参考,具体如下://write txtStringBuilder builder
- 在java程序开发中,ftp用的比较多,经常打交道,比如说向FTP服务器上传文件、下载文件,本文给大家介绍如何利用jakarta commo
- 本文实例讲述了Android编程实现画板功能的方法。分享给大家供大家参考,具体如下:Android实现画板主要有2种方式,一种是用自定义Vi
- 什么是自旋锁说道自旋锁就要从多线程下的锁机制说起,由于在多处理器系统环境中有些资源因为其有限性,有时需要互斥访问(mutual exclus