日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

1 背景

前段時間組內(nèi)針對 “拷貝實例屬性是應(yīng)該用 BeanUtils.copyProperties()還是 MapStruct” 這個問題進行了一次激烈的 battle。支持 MapStruct 的同學(xué)給出了他嫌棄 BeanUtils 的理由:因為用了反射,所以慢。

這個理由一下子拉回了我遙遠的記憶,在我剛開始了解反射這個 JAVA 特性的時候,幾乎看到的每一篇文章都會有 “Java 反射不能頻繁使用”、“反射影響性能” 之類的話語,當時只是當一個結(jié)論記下了這些話,卻沒有深究過為什么,所以正好借此機會來探究一下 Java 反射的代碼。

2 反射包結(jié)構(gòu)梳理

反射相關(guān)的代碼主要在 jdk rt.jar 下的 java.lang.reflect 包下,還有一些相關(guān)類在其他包路徑下,這里先按下不表。按照繼承和實現(xiàn)的關(guān)系先簡單劃分下 java.lang.reflect 包:

① Constructor、Method、Field 三個類型分別可以描述實例的構(gòu)造方法、普通方法和字段。三種類型都直接或間接繼承了 AccessibleObject 這個類型,此類型里主要定義兩種方法,一種是通用的、對訪問權(quán)限進行處理的方法,第二種是可供繼承重寫的、與注解相關(guān)的方法。

② 只看選中的五種類型,我們平常所用到的普通類型,譬如 Integer、String,又或者是我們自定義的類型,都可以用 Class 類型的實例來表示。Java 引入泛型之后,在 JDK1.5 中擴充了其他四種類型,用于泛型的表示。分別是 ParameterizedType (參數(shù)化類型)、WildcardType(通配符類型)、TypeVariable(類型變量)、GenericArrayType(泛型數(shù)組)。

③ 與②中描述的五種基本類型對應(yīng),下圖這五個接口 / 類分別用來表示五種基本類型的注解相關(guān)數(shù)據(jù)。

④ 下圖為實現(xiàn)動態(tài)代理的相關(guān)類與接口。java.lang.reflect.Proxy 主要是利用反射的一些方法獲取代理類的類對象,獲取其構(gòu)造方法,由此構(gòu)造出一個實例。

java.lang.reflect.InvocationHandler 是代理類需要實現(xiàn)的接口,由代理類實現(xiàn)接口內(nèi)的 invoke 方法,此方法會負責(zé)代理流程和被代理流程的執(zhí)行順序組織。

3 目標類實例的構(gòu)造源碼

以 String 類的對象實例化為例,看一下反射是如何進行對象實例化的。

Class<?> clz = Class.forName("java.lang.String");

String s =(String)clz.newInstance();

Class 對象的構(gòu)造由 native 方法完成,以 java.lang.String 類為例,先看看構(gòu)造好的 Class 對象都有哪些屬性:

可以看到目前只有 name 一個屬性有值,其余屬性暫時都是 null 或者默認值的狀態(tài)。

下圖是 clz.newInstance () 方法邏輯的流程圖,接下來對其中主要的兩個方法進行說明:

從上圖可以看出整個流程有兩個核心部分。因為通常情況下,對象的構(gòu)造都需要依靠類里的構(gòu)造方法來實現(xiàn),所以第一部分就是拿到目標類對應(yīng)的 Constructor 對象;第二部分就是利用 Constructor 對象,構(gòu)造目標類的實例。

3.1 獲取 Constructor 對象

首先上一張 Constructor 對象的屬性圖:

java.lang.Class#getConstructor0

此方法中主要做的工作是首先拿到目標類的 Constructor 實例數(shù)組 (主要由 native 方法實現(xiàn)),數(shù)組里每一個對象都代表了目標類的一個構(gòu)造方法。然后對數(shù)組進行遍歷,根據(jù)方法入?yún)⑻峁┑?parameterTypes, 找到符合的 Constructor 對象,然后重新創(chuàng)造一個 Constructor 對象,屬性值與原 Constructor 一致(稱為副本 Constructor),并且副本 Constructor 的屬性 root 指向源 Constructor,相當于對源 Constructor 對象進行了一層封裝。

由于在 getConstructor0 () 方法將返回值返回給調(diào)用方之后,調(diào)用方在后續(xù)的流程里進行了 constructor.setAccesssible (true) 的操作,這個方法的作用是關(guān)閉對 constructor 這個對象訪問時的 Java 語言訪問檢查。語言訪問檢查是個耗時的操作,所以合理猜測是為了提高反射性能關(guān)閉了這個檢查,又出于安全考慮,所以將最原始的對象進行了封裝。

private Constructor<T> getConstructor0(Class<?>[] parameterTypes,

int which) throws NoSuchMethodException

{

//1、拿到Constructor實例數(shù)組并進行篩選

Constructor<T>[] constructors = privateGetDeclaredConstructors((which == Member.PUBLIC));

//2、通過對入?yún)⒌谋容^篩選出符合條件的Constructor

for (Constructor<T> constructor : constructors) {

if (arrayContentsEq(parameterTypes,

constructor.getParameterTypes())) {

//3、創(chuàng)建副本Constructor

return getReflectionFactory().copyConstructor(constructor);

}

}

throw new NoSuchMethodException(getName() + ".<init>" + argumentTypesToString(parameterTypes));

}

3.2 目標類實例的構(gòu)造

sun.reflect.ConstructorAccessor#newInstance

此方法主要是利用上一步創(chuàng)建出來的 Constructor 對象,進行目標類實例的構(gòu)造。Java 為了提高反射的性能,為類實例的構(gòu)造提供了兩種方案,一種是虛擬機自己實現(xiàn)的 native 方法,一種是 JDK 包里的 Java 方法。

首先來看代碼里對 ConstructorAccessor 對象的構(gòu)造,通過代碼可以看出在方法 newConstructorAccessor 中構(gòu)造了 ConstructorAccessor 接口的兩個實現(xiàn)類,兩個對象進行了相互引用,像這樣子:

//構(gòu)造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;

}

}

在調(diào)用 DelegatingConstructorAccessorImpl 的 newInstance 方法時,相當于為 NativeConstructorAccessorImpl 做了一層代理,實際調(diào)用的是 NativeConstructorAccessorImpl 類實現(xiàn)的方法。

public Object newInstance(Object[] var1) throws InstantiationException, IllegalArgumentException, InvocationTargetException {

return this.delegate.newInstance(var1);

}

newInstance 方法中決定使用哪種方法的是一個名為 numInvocations 的 int 類型的變量,每次調(diào)用到 newInstance 方法時,這個變量都會 + 1,當變量值超過閾值(15)時,就會使用 Java 方式進行目標類實例的創(chuàng)造,反之就會使用虛擬機實現(xiàn)的方式進行目標類實例的創(chuàng)造。

這樣做是因為 Java 版本的實現(xiàn)流程很長,其中還包含了字節(jié)碼構(gòu)造的流程,所以初次構(gòu)造比較耗時,但是長久來說性能更好,而 native 版本是初期使用速度較塊,調(diào)用頻繁的話性能會有所下降,所以做了根據(jù)閾值來判斷使用哪個版本的設(shè)計。

public Object newInstance(Object[] var1) throws InstantiationException, IllegalArgumentException, InvocationTargetException {

if (++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.c.getDeclaringClass())) {

//Java方法構(gòu)造對象

ConstructorAccessorImpl var2 = (ConstructorAccessorImpl)(new MethodAccessorGenerator()).generateConstructor(this.c.getDeclaringClass(), this.c.getParameterTypes(), this.c.getExceptionTypes(), this.c.getModifiers());

this.parent.setDelegate(var2);

}

//native方法實現(xiàn)實例化

return newInstance0(this.c, var1);

}

重點關(guān)注以下 Java 版本的實現(xiàn)流程,首先構(gòu)造了一個 ConstructorAccessorImpl 類的對象。這個對象的構(gòu)造主要是依靠在代碼里按照字節(jié)碼文件的格式構(gòu)造出來一個字節(jié)數(shù)組實現(xiàn)的。首先創(chuàng)建了一個 ByteVactor 接口的實現(xiàn)類對象,此類有兩個屬性,一個字節(jié)數(shù)組,一個 int 類型的數(shù)用來標識位置。ClassFileAssembler 類主要負責(zé)把各類值轉(zhuǎn)化成字節(jié)碼的格式然后填充到 ByteVactor 的實現(xiàn)類對象里。最后由 ClassDefiner.defineClass 方法對字節(jié)碼數(shù)組進行處理,構(gòu)造出 ConstructorAccessorImpl 對象。 最后 ConstructorAccessorImpl 實例還是會被傳給 newInstance0 () 這個 native 方法,以此來構(gòu)造最終的目標類實例

private MagicAccessorImpl generate(final Class<?> var1, String var2, Class<?>[] var3, Class<?> var4, Class<?>[] var5, int var6, boolean var7, boolean var8, Class<?> var9) {

//創(chuàng)建ByteVectorImpl對象

ByteVector var10 = ByteVectorFactory.create();

//創(chuàng)建ClassFileAssembler對象

this.asm = new ClassFileAssembler(var10);

var10.trim();

//拿出構(gòu)造好的字節(jié)數(shù)組(就是字節(jié)碼文件的格式)

final byte[] var17 = var10.getData();

return (MagicAccessorImpl)AccessController.doPrivileged(new PrivilegedAction<MagicAccessorImpl>() {

public MagicAccessorImpl run() {

try {

//調(diào)用native方法,創(chuàng)建ConstructorAccessorImpl類的實例

//最后ConstructorAccessorImpl實例還是會被傳給newInstance0()這個native方法,以此來構(gòu)造最終的目標類實例

return (MagicAccessorImpl)ClassDefiner.defineClass(var13, var17, 0, var17.length, var1.getClassLoader()).newInstance();

} catch (IllegalAccessException | InstantiationException var2) {

throw new InternalError(var2);

}

}

});

}

}

4 小結(jié)

最后根據(jù)上述學(xué)習(xí)思考下 Java 反射到底慢不慢這個問題。首先可以看到 JDK 為 “反射時創(chuàng)建對象的過程” 提供了兩套實現(xiàn),native 版本更快但是也使得 JVM 無法對其進行一些優(yōu)化(譬如 JIT 的方法內(nèi)聯(lián)),當方法成為熱點時,轉(zhuǎn)用 Java 版本來進行實現(xiàn)則優(yōu)化了這個問題。但 Java 版本的實現(xiàn)過程中需要動態(tài)生成字節(jié)碼,還要加載一些額外的類,造成了內(nèi)存的消耗,所以使用反射的時候還是應(yīng)當注意一些是否會因為使用過多而造成內(nèi)存溢出。

一次不成熟的源碼學(xué)習(xí)歷程,如有錯誤還請指正。

參考資料:

https://rednaxelafx.iteye.com/blog/548536

 

作者:京東物流 秦曌怡
來源:京東云開發(fā)者社區(qū)

分享到:
標簽:Java
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運動步數(shù)有氧達人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定