掃二維碼與項(xiàng)目經(jīng)理溝通
我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
1. Class 類的原理

孟子曰:得人心者得天下。而在 Java 中,這個(gè)「人心」就是 Class 類,獲取到 Class 類我們就可以為所欲為之為所欲為。下面讓我們深入「人心」,去探索 Class 類的原理。
首先了解 JVM 如何構(gòu)建實(shí)例。
1.1 JVM 構(gòu)建實(shí)例
JVM:Java Virtual Machine,Java 虛擬機(jī)。在 JVM 中分為棧、堆、方法區(qū)等,但這些都是 JVM 內(nèi)存,文中所描述的內(nèi)存指的就是 JVM 內(nèi)存。.class 文件是字節(jié)碼文件,是通過 .java 文件編譯得來的。
知道上面這些內(nèi)容,我們開始創(chuàng)建實(shí)例。我們以創(chuàng)建 Person 對(duì)象舉例:
- Person p = new Person()
簡簡單單通過 new 就創(chuàng)建了對(duì)象,那流程是什么樣的呢?見下圖:
這也太粗糙了一些,那在精致一下吧。
同志們發(fā)現(xiàn)沒有,其實(shí)這里還是有些區(qū)別的,我告訴你區(qū)別是下面的字比上面多,你會(huì)打我不(別打我臉)。
粗糙的那個(gè)是通過 new 創(chuàng)建的對(duì)象,而精致的是通過 ClassLoader 操作 .class 文件生成 Class 類,然后創(chuàng)建的對(duì)象。
其實(shí)通過 new 或者反射創(chuàng)建實(shí)例,都需要 Class 對(duì)象。
1.2 .class 文件
.class 文件在文章開頭講過,是字節(jié)碼文件。.java 是源程序。Java 程序是跨平臺(tái)的,一次編譯到處執(zhí)行,而編譯就是從源文件轉(zhuǎn)換成字節(jié)碼文件。
字節(jié)碼無非就是由 0 和 1 構(gòu)成的文件。
有如下一個(gè)類:
通過 vim 查看一下字節(jié)碼文件:
這啥玩意,看不懂。咱也不需要看懂,反正 JVM 對(duì) .class 文件有它自己的讀取規(guī)則。
1.3 類加載器
還記得上面的精致圖中,我們知道是通過類加載器把 .class 文件加載到內(nèi)存中。具體的類加載器內(nèi)容,我會(huì)另寫一篇文章講解(寫完鏈接會(huì)更新到這里)。但是核心方法就是 loadClass(),只需要告訴它要加載的 name,它就會(huì)幫你加載:
- protected Class> loadClass(String name, boolean resolve)
- throws ClassNotFoundException
- {
- synchronized (getClassLoadingLock(name)) {
- // 1.檢查類是否已經(jīng)加載
- Class> c = findLoadedClass(name);
- if (c == null) {
- long t0 = System.nanoTime();
- try {
- // 2.尚未加載,遵循父優(yōu)先的等級(jí)加載機(jī)制(雙親委派機(jī)制)
- 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) {
- // 3.如果還沒有加載成功,調(diào)用 findClass()
- 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;
- }
- }
- // 需要重寫該方法,默認(rèn)就是拋出異常
- protected Class> findClass(String name) throws ClassNotFoundException {
- throw new ClassNotFoundException(name);
- }
上面兩步都失敗,調(diào)用 findClass()
因?yàn)?ClassLoader 的 findClass 方法默認(rèn)拋出異常,需要我們寫一個(gè)子類重新覆蓋它,比如:
- @Override
- protected Class> findClass(String name) throws ClassNotFoundException {
- try {
- // 通過IO流從指定位置讀取xxx.class文件得到字節(jié)數(shù)組
- byte[] datas = getClassData(name);
- if (null == datas){
- throw new ClassNotFoundException("類沒有找到:" + name);
- }
- // 調(diào)用類加載器本身的defineClass()方法,由字節(jié)碼得到 class 對(duì)象
- return defineClass(name, datas, 0, datas.length);
- }catch (IOException e){
- throw new ClassNotFoundException("類沒有找到:" + name);
- }
- }
- private byte[] getClassData(String name) {
- return byte[] datas;
- }
defineClass 是通過字節(jié)碼獲取 Class 的方法,是 ClassLoader 定義的。我們具體不知道如何實(shí)現(xiàn)的,因?yàn)樽罱K會(huì)調(diào)用一個(gè) native 方法:
- private native Class> defineClass0(String name, byte[] b, int off, int len,
- ProtectionDomain pd);
- private native Class> defineClass1(String name, byte[] b, int off, int len,
- ProtectionDomain pd, String source);
- private native Class> defineClass2(String name, java.nio.ByteBuffer b,
- int off, int len, ProtectionDomain pd,
- String source);
總結(jié)下類加載器加載 .class 文件的步驟:
1.4 Class 類
.class 文件已經(jīng)被類加載器加載到內(nèi)存中并生成字節(jié)數(shù)組,JVM 根據(jù)字節(jié)數(shù)組創(chuàng)建了對(duì)應(yīng)的 Class 對(duì)象。
接下來我們來分析下 Class 對(duì)象。
我們知道 Java 的對(duì)象會(huì)有下面的信息:
這些信息在 .class 文件以 0101 表示,最后 JVM 會(huì)把 .class 文件的信息通過它的方式保存到 Class 中。
在 Class 中肯定有保存這些信息的字段,我們來看一下:
Class 類中用 ReflectionData 里面的字段來與 .class 的內(nèi)容映射,分別映射了字段、方法、構(gòu)造器和接口。
通過 annotaionData 映射了注解數(shù)據(jù),其它的就不展示了,大家可以自行打開 IDEA 查看下 Class 的源碼。
那我們看看 Class 類的方法
1.4.1 構(gòu)造器
Class 類的構(gòu)造器是私有的,只能通過 JVM 創(chuàng)建 Class 對(duì)象。所以就有了上面通過類加載器獲取 Class 對(duì)象的過程。
1.4.2 Class.forName
Class.forName() 方法還是通過類加載器獲取 Class 對(duì)象。
1.4.3 newInstance
newInstance() 的底層是返回?zé)o參構(gòu)造函數(shù)。
2. 總結(jié)
我們來梳理下前面的知識(shí)點(diǎn):
反射的關(guān)鍵點(diǎn)就是獲取 Class 類,那系統(tǒng)是如何獲取到 Class 類?
是通過類加載器 ClassLoader 將 .class 文件通過字節(jié)數(shù)組的方式加載到 JVM 中,JVM 將字節(jié)數(shù)組轉(zhuǎn)換成 Class 對(duì)象。那類加載器是如何加載的呢?
Class 類的構(gòu)造器是私有的,所以需要通過 JVM 獲取 Class。
Class.forName() 也是通過類加載器獲取的 Class 對(duì)象。newInstance 方法的底層也是返回的無參構(gòu)造函數(shù)。

我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流