建議收藏!超詳細的JVM反射原理技術點總結

2020-10-10 java架構師阿松

反射定義

1,java反射機制是在運行狀態中

對於任意一個類,都能夠知道這個類的所有屬性和方法;

對於任意一個對象,都能夠調用它的任意一個方法和屬性;

這種動態獲取的信息以及動態調用對象的方法的功能稱為java語言的反射機制。

反射提供的功能:

  • 在運行時判斷任意一個對象所屬的類
  • 在運行時構造任意一個類的對象
  • 在運行時判斷任意一個類所具有的成員變量和方法
  • 在運行時調用任意一個對象的方法

(如果屬性是private,正常情況下是不允許外界操作屬性值,這裡可以用Field類的setAccessible(true)方法,暫時打開操作的權限)

反射的使用場景

  • Java編碼時知道類和對象的具體信息,此時直接對類和對象進行操作即可,無需反射
  • 如果編碼時不知道類或者對象的具體信息,此時應該使用反射來實現

反射源碼解析

舉例API :

Class.forName("com.my.reflectTest").newInstance()

1. 反射獲取類實例 Class.forName("xxx");

  首先調用了 java.lang.Class 的靜態方法,獲取類信息!

注意:forName()反射獲取類信息,並沒有將實現留給了java,而是交給了jvm去加載!

主要是先獲取 ClassLoader, 然後調用 native 方法,獲取信息,加載類則是回調 入參ClassLoader 進類加載!

@CallerSensitive public static Class<?> forName(String className) throws ClassNotFoundException { // 先通過反射,獲取調用進來的類信息,從而獲取當前的 classLoader Class<?> caller = Reflection.getCallerClass(); // 調用native方法進行獲取class信息 return forName0(className, true, ClassLoader.getClassLoader(caller), caller); }

2. java.lang.ClassLoader-----loadClass()

// java.lang.ClassLoader protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // 先獲取鎖 synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded // 如果已經加載了的話,就不用再加載了 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { // 雙親委託加載 if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } // 父類沒有加載到時,再自己加載 if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } } protected Object getClassLoadingLock(String className) { Object lock = this; if (parallelLockMap != null) { // 使用 ConcurrentHashMap來保存鎖 Object newLock = new Object(); lock = parallelLockMap.putIfAbsent(className, newLock); if (lock == null) { lock = newLock; } } return lock; } protected final Class<?> findLoadedClass(String name) { if (!checkName(name)) return null; return findLoadedClass0(name); }

3. newInstance()

newInstance() 其實相當於調用類的無參構造函數,主要做了三件事

  • 權限檢測,如果不通過直接拋出異常;
  • 查找無參構造器,並將其緩存起來;
  • 調用具體方法的無參構造方法,生成實例並返回;

// 首先肯定是 Class.newInstance @CallerSensitive public T newInstance() throws InstantiationException, IllegalAccessException { if (System.getSecurityManager() != null) { checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), false); } // NOTE: the following code may not be strictly correct under // the current Java memory model. // Constructor lookup // newInstance() 其實相當於調用類的無參構造函數,所以,首先要找到其無參構造器 if (cachedConstructor == null) { if (this == Class.class) { // 不允許調用 Class 的 newInstance() 方法 throw new IllegalAccessException( "Can not call newInstance() on the Class for java.lang.Class" ); } try { // 獲取無參構造器 Class<?>[] empty = {}; final Constructor<T> c = getConstructor0(empty, Member.DECLARED); // Disable accessibility checks on the constructor // since we have to do the security check here anyway // (the stack depth is wrong for the Constructor's // security check to work) java.security.AccessController.doPrivileged( new java.security.PrivilegedAction<Void>() { public Void run() { c.setAccessible(true); return null; } }); cachedConstructor = c; } catch (NoSuchMethodException e) { throw (InstantiationException) new InstantiationException(getName()).initCause(e); } } Constructor<T> tmpConstructor = cachedConstructor; // Security check (same as in java.lang.reflect.Constructor) int modifiers = tmpConstructor.getModifiers(); if (!Reflection.quickCheckMemberAccess(this, modifiers)) { Class<?> caller = Reflection.getCallerClass(); if (newInstanceCallerCache != caller) { Reflection.ensureMemberAccess(caller, this, null, modifiers); newInstanceCallerCache = caller; } } // Run constructor try { // 調用無參構造器 return tmpConstructor.newInstance((Object[])null); } catch (InvocationTargetException e) { Unsafe.getUnsafe().throwException(e.getTargetException()); // Not reached return null; } }

4. getConstructor0() 為獲取匹配的構造方器;分三步:

  1. 先獲取所有的constructors, 然後通過進行參數類型比較;   2. 找到匹配後,通過 ReflectionFactory copy一份constructor返回;   3. 否則拋出 NoSuchMethodException;

private Constructor<T> getConstructor0(Class<?>[] parameterTypes, int which) throws NoSuchMethodException { // 獲取所有構造器 Constructor<T>[] constructors = privateGetDeclaredConstructors((which == Member.PUBLIC)); for (Constructor<T> constructor : constructors) { if (arrayContentsEq(parameterTypes, constructor.getParameterTypes())) { return getReflectionFactory().copyConstructor(constructor); } } throw new NoSuchMethodException(getName() + ".<init>" + argumentTypesToString(parameterTypes)); }

5. privateGetDeclaredConstructors(), 獲取所有的構造器主要步驟;

  1. 先嘗試從緩存中獲取;   2. 如果緩存沒有,則從jvm中重新獲取,並存入緩存,緩存使用軟引用進行保存,保證內存可用;

// 獲取當前類所有的構造方法,通過jvm或者緩存 // Returns an array of "root" constructors. These Constructor // objects must NOT be propagated to the outside world, but must // instead be copied via ReflectionFactory.copyConstructor. private Constructor<T>[] privateGetDeclaredConstructors(boolean publicOnly) { checkInitted(); Constructor<T>[] res; // 調用 reflectionData(), 獲取保存的信息,使用軟引用保存,從而使內存不夠可以回收 ReflectionData<T> rd = reflectionData(); if (rd != null) { res = publicOnly ? rd.publicConstructors : rd.declaredConstructors; // 存在緩存,則直接返回 if (res != null) return res; } // No cached value available; request value from VM if (isInterface()) { @SuppressWarnings("unchecked") Constructor<T>[] temporaryRes = (Constructor<T>[]) new Constructor<?>[0]; res = temporaryRes; } else { // 使用native方法從jvm獲取構造器 res = getDeclaredConstructors0(publicOnly); } if (rd != null) { // 最後,將從jvm中讀取的內容,存入緩存 if (publicOnly) { rd.publicConstructors = res; } else { rd.declaredConstructors = res; } } return res; } // Lazily create and cache ReflectionData private ReflectionData<T> reflectionData() { SoftReference<ReflectionData<T>> reflectionData = this.reflectionData; int classRedefinedCount = this.classRedefinedCount; ReflectionData<T> rd; if (useCaches && reflectionData != null && (rd = reflectionData.get()) != null && rd.redefinedCount == classRedefinedCount) { return rd; } // else no SoftReference or cleared SoftReference or stale ReflectionData // -> create and replace new instance return newReflectionData(reflectionData, classRedefinedCount); } // 新創建緩存,保存反射信息 private ReflectionData<T> newReflectionData(SoftReference<ReflectionData<T>> oldReflectionData, int classRedefinedCount) { if (!useCaches) return null; // 使用cas保證更新的線程安全性,所以反射是保證線程安全的 while (true) { ReflectionData<T> rd = new ReflectionData<>(classRedefinedCount); // try to CAS it... if (Atomic.casReflectionData(this, oldReflectionData, new SoftReference<>(rd))) { return rd; } // 先使用CAS更新,如果更新成功,則立即返回,否則測查當前已被其他線程更新的情況,如果和自己想要更新的狀態一致,則也算是成功了 oldReflectionData = this.reflectionData; classRedefinedCount = this.classRedefinedCount; if (oldReflectionData != null && (rd = oldReflectionData.get()) != null && rd.redefinedCount == classRedefinedCount) { return rd; } } }

另外,使用 relactionData() 進行緩存保存;ReflectionData 的數據結構如下!

// reflection data that might get invalidated when JVM TI RedefineClasses() is called private static class ReflectionData<T> { volatile Field[] declaredFields; volatile Field[] publicFields; volatile Method[] declaredMethods; volatile Method[] publicMethods; volatile Constructor<T>[] declaredConstructors; volatile Constructor<T>[] publicConstructors; // Intermediate results for getFields and getMethods volatile Field[] declaredPublicFields; volatile Method[] declaredPublicMethods; volatile Class<?>[] interfaces; // Value of classRedefinedCount when we created this ReflectionData instance final int redefinedCount; ReflectionData(int redefinedCount) { this.redefinedCount = redefinedCount; } }

6.通過上面,獲取到 Constructor 了!接下來就只需調用其相應構造器的 newInstance(),即返回實例了!

// return tmpConstructor.newInstance((Object[])null); // java.lang.reflect.Constructor @CallerSensitive public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (!override) { if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class<?> caller = Reflection.getCallerClass(); checkAccess(caller, clazz, null, modifiers); } } if ((clazz.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("Cannot reflectively create enum objects"); ConstructorAccessor ca = constructorAccessor; // read volatile if (ca == null) { ca = acquireConstructorAccessor(); } @SuppressWarnings("unchecked") T inst = (T) ca.newInstance(initargs); return inst; } // sun.reflect.DelegatingConstructorAccessorImpl public Object newInstance(Object[] args) throws InstantiationException, IllegalArgumentException, InvocationTargetException { return delegate.newInstance(args); } // sun.reflect.NativeConstructorAccessorImpl public Object newInstance(Object[] args) throws InstantiationException, IllegalArgumentException, InvocationTargetException { // We can't inflate a constructor belonging to a vm-anonymous class // because that kind of class can't be referred to by name, hence can't // be found from the generated bytecode. if (++numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(c.getDeclaringClass())) { ConstructorAccessorImpl acc = (ConstructorAccessorImpl) new MethodAccessorGenerator(). generateConstructor(c.getDeclaringClass(), c.getParameterTypes(), c.getExceptionTypes(), c.getModifiers()); parent.setDelegate(acc); } // 調用native方法,進行調用 constructor return newInstance0(c, args); }

返回構造器的實例後,可以根據外部進行進行類型轉換,從而使用接口或方法進行調用實例功能了。

總結

歡迎關注公眾號:前程有光,領取一線大廠Java面試題總結+各知識點學習思維導+一份300頁pdf文檔的Java核心知識點總結!


這些資料的內容都是面試時面試官必問的知識點,篇章包括了很多知識點,其中包括了有基礎知識、Java集合、JVM、多線程並發、spring原理、微服務、Netty 與RPC 、Kafka、日記、設計模式、Java算法、資料庫、Zookeeper、分布式緩存、數據結構等等。

相關焦點

  • Java面試總結之JVM
    這篇文章我會根據在實際面試過程中遇到的考題,然後結合理論知識點,總結一下JVM的實戰考點。有5種方法可以完成初始化:1.調用new方法,2.使用Class類的newInstance方法(反射機制),3.使用Constructor類的newInstance方法(反射機制),4.使用Clone方法創建對象,5.使用(反)序列化機制創建對象使用:完成類的初始化後,就可以對類進行實例化,在程序中進行使用了卸載:當類被加載,連接和初始化後,它的生命周期就始了,當代表類的
  • JVM CPU Profiler技術原理及源碼深度解析
    本文介紹了JVM平臺上CPU Profiler的實現原理,希望能幫助讀者在使用類似工具的同時也能清楚其內部的技術實現。引言研發人員在遇到線上報警或需要優化系統性能時,常常需要分析程序運行行為和性能瓶頸。
  • 網易高薪架構師技術心得:Java反射機制的核心原理
    反射是Java的高級特性之一,在底層框架中被頻繁的使用。比如:JDBC中的加載資料庫驅動程序,Spring框架中加載bean對象,以及態代理,這些都使用到反射,因為我們要想理解一些框架的底層原理,Java反射是我們必須要掌握的。
  • 面試官這樣問我Java反射原理,我剛好都會
    這周美團面試官給我進行了面試,面試過程中他問了Java的反射原理。(不得不誇一句,美團的效率真高,上午面完一面,晚上二面馬上安排上了。)無論什麼Java技術崗位,Java反射原理,在面試中出現的次數很多,所以我面試之前也有所準備,於是今天結合面試問題先詳細講一講Java反射原理。
  • JVM實戰:Metaspace內存溢出排查與總結
    這個值jvm默認是1000ms,如果被設置為0,就會導致軟引用對象馬上被回收掉,進而會導致重新頻繁的生成新的類,而無法達到復用的效果。總結目前主要是通過修改JVM的-XX:SoftRefLRUPolicyMSPerMB值來解決metaspace上升問題,後續會持續觀察變化,適當調整參數。
  • 百度-網易-字節跳動 面試題總結分享「建議收藏」
    掌握技術有限,學習主動性一般(雖遇到問題善於思考鑽研,但如果沒有遇到問題比較放縱自己...被自由,輕鬆,安逸迷失心智),以及原公司項目背景因素,基本不需要太多新的技術框架支持,更沒有涉及消息中間件,高並發分布式等等相關較主流技術,技術體系相對較老。綜上導致我對很多新技術並不是太了解。
  • JVM加載class文件的原理機制詳解
    ,比如網站、企業內部應用、實時交易系統等等,直到某一天突然發現做的系統咋就這麼慢呢,而且時不時還來個內存溢出什麼的,今天是交易系統報了StackOverflowError ,明天是網站系統報了個OutOfMemoryError ,這種錯誤又很難重現,只有分析Javacore 和dump 文件,運氣好點還能分析出個結果,運行的點,就直接去廟裡燒香吧!
  • 超詳細的阿里字節Spring面試技術點總結(建議收藏)
    前言Spring作為現在最流行Java開發技術,其內部源碼設計非常優秀。做程式設計師難,做一個2020年的程式設計師更難,隨著IT人員越來越多,我們的競爭壓力也越來越大,想要在茫茫人海中脫穎而出,其實考察的就是我們技術棧的廣度和深度
  • 阿里Java面試題目大匯總(強烈建議收藏)
    、個人擅長的項目)重點是面試技術原理,以及對技術的熱情和專研程度:Java的高級知識開源框架的原理JVM多線程高並發中間件之前項目經歷,運用的技術,遇到的問題,如何解決,個人有什麼收穫和成長;對於技術的熱情(平時是否看些技術書籍,逛論壇,寫博客
  • GC慘案引發對反射原理的思考
    這主要是由於存在大量反射而產生的臨時類加載器和 ASM 臨時生成的類,這些類會被保留在 Metaspace,一旦 Metaspace 即將滿的時候,就會觸發 FullGc,已達到回收不再被使用的類對象的目的。具體問題請參考接下來的內容,更好的了解反射的實現原理。
  • JVM入門(一)類的加載過程
    在講JVM最開始,我們先以一個簡單的Java程序的運行開始講,JAVA程序的運行原理。無論是那種方式,當我們執行的時候,就會啟動jvm虛擬機去加載所要執行的Hello.class文件到虛擬機中。然後在jvm中有字節碼執行引擎負責去執行Hello.class中的main方法,在main方法中使用到了Person類,此時jvm又會去加載Person。也就是說jvm用到哪個類,然後就去加載哪個類。
  • JVM層面的切面實現 : jvm-sandbox 之 <應用啟動>
    注意,SandboxClassLoader位於sandbox-agent.jar包中,其父類加載器為AppClassLoader使用SandboxClassLoader反射加載並實例化sandbox-core.jar裡的com.alibaba.jvm.sandbox.core.CoreConfigure,傳入第1步的參數以及sandbox.properties文件的路徑
  • 面試必問億級流量優化策略之JVM調優,文檔視頻面試,還不收藏
    如果你不想一直做默默無聞的 CRUDer,如果你想在團隊有擔當,或者你願望很簡單——就想錢多事少離家近,那建議你一定要啃下性能調優。聽下來,你可以收穫:1. 徹底掌握JVM最底層原理,應對大廠面試從容不迫2.
  • 繪畫中的透視原理是什麼?透視原理超詳細講解
    繪畫中的透視原理是什麼?透視原理超詳細講解!一二三點透視分別怎麼用?消失點、俯瞰,啥跟啥啊,頭都大了!相信這是不少初學畫畫的同學的心聲。之前輕微課繪畫網校小編也針對透視問題出過一些文章,但還是不停有同學私我關於透視的知識。
  • 建議收藏!清華大佬親授JVM虛擬機中的垃圾回收理論與實戰筆記
    PrintGCApplicationConcurrentTime (低)列印應用程式時間* -XX:+PrintGCApplicationStoppedTime (低)列印暫停時長* -XX:+PrintReferenceGC (重要性低)記錄回收了多少種不同引用類型的引用* -verbose:class類加載詳細過程
  • 【建議收藏】韓國誕生職業攝像頭獵人 簡直是防不勝防!該怎麼預防...
    中已詳細介紹,請收藏並對照查閱。 針孔偷拍設備的鏡頭雖然只有繡花針針孔大小,但偷拍設備的其他組件(如存儲器、發射器、線纜、電池等)最小也需要打火機大小,因此在正對床鋪的位置或洗手間內,去找能夠容納打火機大小的日用品、電器即可。
  • 多點觸控技術的原理和意義
    導語:多點觸控技術的橫空出世是伴隨著智慧型手機等大屏觸摸設備的興起而出現的,並且在短短的數年間成為一 門 推廣開來的技術。但是多點觸控技術的原理究竟是什麼,它的意義又是哪些,您知道嗎?多點觸控技術是基於受抑內全發射技術這個技術的基礎上形成的,並且開始逐漸發展成型。既然在現今的生活中,我們的電子設備已經離不開多點觸控技術,那我們為何不來了解一下多點觸控技術呢?
  • 2020年史上最全最新阿里1000道Java面試題目大匯總(強烈建議收藏)
    阿里技術二面(技術原理、個人擅長的項目)重點是面試技術原理,以及對技術的熱情和專研程度:Java的高級知識開源框架的原理JVM多線程高並發中間件之前項目經理,運用的技術,遇到的問題,如何解決,個人有什麼收穫和成長
  • Spring Boot全面總結(超詳細,建議收藏)
    前言:本文非常長,建議先mark後看,也許是最後一次寫這麼長的文章說明:前面有4個小節關於Spring的基礎知識分別是:IOC容器、JavaConfig、事件監聽、SpringFactoriesLoader詳解它們佔據了本文的大部分內容:
  • 三大步驟,一周啃透了JVM,順利拿下字節跳動研發崗Offer
    現在各大平臺上關於jvm的文章有很多,不過寫的有些偏基礎,並不是很全面。道經典面試題內存模型以及分區,需要詳細到每個區放什麼。GC 的兩種判定方法:SafePoint 是什麼GC 的三種收集方法:標記清除、標記整理、複製算法的原理與特點,分別用在什麼地方,如果讓你優化收集方法,有什麼思路?GC 收集器有哪些?CMS 收集器與 G1 收集器的特點。Minor GC 與 Full GC 分別在什麼時候發生?