Java反射及性能詳細(xì)
我們今天不探討框架層面的內(nèi)容,暫且認(rèn)為90%的框架不存在無法容忍的性能問題。在做系統(tǒng)調(diào)優(yōu)的過程中,面對(duì)隨處可見的invoke調(diào)用,我的內(nèi)心其實(shí)是比較抵觸的,倒不是說反射怎么不好,對(duì)于優(yōu)雅的源碼來說,反射必不可少,個(gè)人抵觸的原因主要是因?yàn)榉瓷浒颜鎸?shí)的方法“隱藏”的很好,面對(duì)長(zhǎng)長(zhǎng)的線程棧比較頭大而已。而且我心里一直有個(gè)大大的問號(hào),反射到底存在哪些性能問題。
帶著這個(gè)疑惑,基于java最基本的反射使用,通過查看資料及源碼閱讀,有如下的總結(jié)和分享,歡迎交流和指正。
一、準(zhǔn)備
注:本案例針對(duì)JDK1.8
測(cè)試代碼:
【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執(zhí)行類加載...");
}
public CommonTestEntity() {
System.out.println(this.getClass() + " | CommonTestEntity實(shí)例初始化 | " + this.getClass().getClassLoader());
}
public void defaultMethod() {
System.out.println("執(zhí)行實(shí)例方法:defaultMethod");
}
}
二、反射調(diào)用流程
1.反射的使用
- 1)創(chuàng)建
class對(duì)象(類加載,使用當(dāng)前方法所在類的ClassLoader來加載) - 2)獲取
Method對(duì)象(getMethod和getDeclaredMethod) - 3)調(diào)用
invoke方法
2.getMethod 和 getDeclaredMethod區(qū)別
getMethod源碼如下:
public Method getMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
Objects.requireNonNull(name);
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// 1. 檢查方法權(quán)限
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. 檢查方法是權(quán)限
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.檢查方法權(quán)限
- b.獲取方法
Method對(duì)象 - c.返回方法
主要有兩個(gè)區(qū)別:
1.getMethod 中 checkMemberAccess 傳入的是 Member.PUBLIC,而 getDeclaredMethod 傳入的是 Member.DECLARED 。
代碼中的注釋:

注釋里解釋了 PUBLIC 和 DECLARED 的不同,PUBLIC 會(huì)包括所有的 public 方法,包括父類的方法,而 DECLARED 會(huì)包括所有自己定義的方法,public,protected,private 都在此,但是不包括父類的方法。
2.getMethod 中獲取方法調(diào)用的是 getMethod0,而 getDeclaredMethod 獲取方法調(diào)用的是 privateGetDeclaredMethods 。privateGetDeclaredMethods 是獲取類自身定義的方法,參數(shù)是 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方法中也會(huì)調(diào)用到privateGetDeclaredMethods方法和searchMethods方法
3.getMethod 方法流程
4.getDeclaredMethod方法流程
三、調(diào)用反射方法
invoke源碼:
class Method {
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (!override) {
Class<?> caller = Reflection.getCallerClass();
// 1. 檢查權(quán)限
checkAccess(caller, clazz,
Modifier.isStatic(modifiers) ? null : obj.getClass(),
modifiers);
}
// 2. 獲取 MethodAccessor
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
// 創(chuàng)建 MethodAccessor
ma = acquireMethodAccessor();
}
// 3. 調(diào)用 MethodAccessor.invoke
return ma.invoke(obj, args);
}
}
Method.invoke()實(shí)際上并不是自己實(shí)現(xiàn)的反射調(diào)用邏輯,而是委托給sun.reflect.MethodAccessor來處理。
每個(gè)實(shí)際的Java方法只有一個(gè)對(duì)應(yīng)的Method對(duì)象作為root(實(shí)質(zhì)上就是Method類的一個(gè)成員變量)。每次在通過反射獲取Method對(duì)象時(shí)新創(chuàng)建Method對(duì)象把root封裝起來。在第一次調(diào)用一個(gè)實(shí)際Java方法對(duì)應(yīng)得Method對(duì)象的invoke()方法之前,實(shí)現(xiàn)調(diào)用邏輯的MethodAccessor對(duì)象是第一次調(diào)用時(shí)才會(huì)新建并更新給root,然后調(diào)用MethodAccessor.invoke()真正完成反射調(diào)用。
MethodAccessor只是單方法接口,其invoke()方法與Method.invoke()的對(duì)應(yīng)。創(chuàng)建MethodAccessor實(shí)例的是ReflectionFactory。
MethodAccessor實(shí)現(xiàn)有兩個(gè)版本,一個(gè)是Java實(shí)現(xiàn)的,另一個(gè)是native code實(shí)現(xiàn)的。
Java 版本的 MethodAccessorImpl 調(diào)用效率比 Native 版本要快 20 倍以上,但是 Java 版本加載時(shí)要比 Native 多消耗 3-4 倍資源,所以默認(rèn)會(huì)調(diào)用 Native 版本,如果調(diào)用次數(shù)超過 15 次以后,就會(huì)選擇運(yùn)行效率更高的 Java 版本。
Native版本中的閾值(靜態(tài)常量)
四、反射效率低的原因
1.Method#invoke 方法會(huì)對(duì)參數(shù)做封裝和解封操作
我們可以看到,
invoke方法的參數(shù)是Object[]類型,也就是說,如果方法參數(shù)是簡(jiǎn)單類型(8中基本數(shù)據(jù)類型)的話,需要在此轉(zhuǎn)化成 Object 類型,例如 long ,在javac compile的時(shí)候 用了Long.valueOf()轉(zhuǎn)型,也就大量了生成了Long的 Object, 同時(shí) 傳入的參數(shù)是Object[]數(shù)值,那還需要額外封裝object數(shù)組。而在上面
MethodAccessorGenerator#emitInvoke方法里我們看到,生成的字節(jié)碼時(shí),會(huì)把參數(shù)數(shù)組拆解開來,把參數(shù)恢復(fù)到?jīng)]有被 Object[] 包裝前的樣子,同時(shí)還要對(duì)參數(shù)做校驗(yàn),這里就涉及到了解封操作。因此,在反射調(diào)用的時(shí)候,因?yàn)榉庋b和解封,產(chǎn)生了額外的不必要的內(nèi)存浪費(fèi),當(dāng)調(diào)用次數(shù)達(dá)到一定量的時(shí)候,還會(huì)導(dǎo)致 GC。
2.需要檢查方法可見性
checkAccess方法
3.需要遍歷方法并校驗(yàn)參數(shù)
PrivateGetMethodRecursive中的searhMethod
4.JIT 無法優(yōu)化
在 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.
五、反射優(yōu)化
1.(網(wǎng)上看到)盡量不要getMethods()后再遍歷篩選,而直接用getMethod(methodName)來根據(jù)方法名獲取方法
但是在源碼中獲取方法的時(shí)候,在searchMethods方法中,其實(shí)也是采用遍歷所有方法的方式。但是相比getMethod,getDeclaredMethod遍歷的方法數(shù)量相對(duì)較少,因?yàn)椴话割惖姆椒ā?/p>
2.緩存class對(duì)象
a)Class.forName性能比較差
b)如上所述,在獲取具體方法時(shí),每次都要調(diào)用native方法獲取方法列表并遍歷列表,判斷入?yún)㈩愋秃头祷仡愋?。將反射得到?code>method/field/constructor對(duì)象做緩存,將極大的提高性能。
3.涉及動(dòng)態(tài)代理的:在實(shí)際使用中,CGLIB和Javassist基于動(dòng)態(tài)代碼的代理實(shí)現(xiàn),性能要優(yōu)于JDK自帶的動(dòng)態(tài)代理
JDK自帶的動(dòng)態(tài)代理是基于接口的動(dòng)態(tài)代理,相比較直接的反射操作,性能還是高很多,因?yàn)榻涌趯?shí)例相關(guān)元數(shù)據(jù)在靜態(tài)代碼塊中創(chuàng)建并且已經(jīng)緩存在類成員屬性中,在運(yùn)行期間是直接調(diào)用,沒有額外的反射開銷。
4.使用ReflectASM,通過生成字節(jié)碼的方式加快反射(使用難度大)
到此這篇關(guān)于Java反射及性能詳細(xì)的文章就介紹到這了,更多相關(guān)Java反射及性能內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring boot中@Conditional和spring boot的自動(dòng)配置實(shí)例詳解
本文通過實(shí)例給大家介紹了Spring boot中@Conditional和spring boot的自動(dòng)配置,需要的朋友可以參考下2018-05-05
SSM框架中entity mapper dao service controll
這篇文章主要介紹了SSM框架中entity mapper dao service controller層的使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11
使用httpclient無需證書調(diào)用https的示例(java調(diào)用https)
這篇文章主要介紹了使用httpclient無需證書調(diào)用https的示例(java調(diào)用https),需要的朋友可以參考下2014-04-04
SpringBoot整合SpringSecurity認(rèn)證與授權(quán)
在項(xiàng)目開發(fā)中,權(quán)限認(rèn)證是很重要的,尤其是一些管理類的系統(tǒng),對(duì)于權(quán)限要求更為嚴(yán)格,本文主要介紹了SpringBoot整合SpringSecurity認(rèn)證與授權(quán),感興趣的可以了解一下2023-11-11
Spring Boot實(shí)現(xiàn)郵件發(fā)送功能
這篇文章主要為大家詳細(xì)介紹了Spring Boot實(shí)現(xiàn)郵件發(fā)送功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06
詳解Java8新特性之interface中的static方法和default方法
這篇文章主要介紹了Java8新特性之interface中的static方法和default方法,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-08-08
把spring boot項(xiàng)目發(fā)布tomcat容器(包含發(fā)布到tomcat6的方法)
這篇文章主要介紹了把spring boot項(xiàng)目發(fā)布tomcat容器(包含發(fā)布到tomcat6的方法),然后在文章給大家提到了如何將Spring Boot項(xiàng)目打包部署到外部Tomcat,需要的朋友參考下吧2017-11-11

