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