一、類文件
1、結(jié)構:
ClassFile {
u4 magic;// 魔數(shù),用于確定文件類型
u2 minor_version;// 副版本號
u2 major_version;// 主版本號,代表JDK版本,如:0034,十進制為52,表示JDK8
u2 constant_pool_count;// 常量池數(shù)量
cp_info constant_pool[ constant_pool_count - 1];// 常量池
u2 access_flags;// 標記,識別類是class還是接口;是否是public;是否是abstract;是否是final等
u2 this_class;// 當前類
u2 super_class;// 父類
u2 interfaces_count;// 接口數(shù)量
u2 interfaces[ interfaces_count];// 接口
u2 fields_count;// 字段數(shù)量
field_info fields[ fields_count];// 字段
u2 methods_count;// 方法數(shù)量
method_info methods[ methods_count];// 方法
u2 attributes_count;// 屬性數(shù)量
attribute_info attributes[ attributes_count];// 屬性
}
文件通過二進制存儲,以8個字節(jié)為一組,下圖為16進制視圖下類文件的內(nèi)容:
具體內(nèi)容及常量池結(jié)構等,可以參考Java Language and Virtual Machine Specifications。
2、生命周期
類從被加載到虛擬機內(nèi)存到從內(nèi)存卸載,包含以下幾個階段:
(1)加載
- 通過全類名獲取類的二進制數(shù)據(jù)流。
- 將類的二進制數(shù)據(jù)解析為方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構Klass。
- 在堆區(qū)創(chuàng)建java.lang.Class類的實例。
- 數(shù)組類本身不通過類加載器創(chuàng)建,它是由 Java 虛擬機直接創(chuàng)建的。如果數(shù)組元素是引用類型,則會先加載該引用類型,再創(chuàng)建數(shù)組。
(2)驗證
- 為確保class信息符合當前虛擬機的要求,做以下驗證:格式檢查、語義檢查、字節(jié)碼驗證、符號引用驗證。
(3)準備
- 為類變量(static)分配內(nèi)存,并初始化為默認值。
- static final修飾的屬性為常量,在常量池初始化的時候確認內(nèi)存。
(4)解析
- 把類、接口、字段、方法的符號引用替換成直接引用。
- class文件中不會保存各個方法和字段最終內(nèi)存布局信息,這些符號引用不經(jīng)過轉(zhuǎn)換就無法直接被虛擬機使用。
- 符號引用以一組符號來描述所引用的目標,只要使用時能無歧義地定位到目標即可。
- 直接引用可以是直接指向目標的指針、相對偏移量或是一個能間接定位到目標的句柄。
(5)初始化
- 為類變量賦予正確的初始化值
- 初始化階段會執(zhí)行<clinit>(),該方法是javac編譯器收集類變量的賦值動作和靜態(tài)代碼快中的語句合并而來的。
(6)使用
- 經(jīng)歷過上述階段,類就可以使用了。
- 可以在程序中訪問和調(diào)用它的靜態(tài)類成員信息,或者通過new創(chuàng)建對象實例。
(7)卸載
- 常量回收:常量沒有被任何地方引用的時候,則被標記為廢棄常量。
- 類回收:Java堆中不存在該類的任何實例對象;加載該類的類加載器已經(jīng)被回收;該類對應的java.lang.Class對象不在任何地方被引用,且無法在任何地方通過反射訪問該類的方法;則類可以被回收。
二、類加載器
負責讀取指定目錄下的class字節(jié)碼文件,并進行解析,然后轉(zhuǎn)換成方法區(qū)的響應結(jié)構。
1、分類
類加載器分為兩類:啟動(引導)類加載器(Bootstrap ClassLoader)、自定義類加載器(User-Defined ClassLoader)。Java虛擬機規(guī)范將所有派生于抽象類ClassLoader的類加載器都劃分為自定義類加載器。
注意:父加載器和繼承沒有關系,是類加載器中的parent字段。
(1)啟動(引導)類加載器 BootstrapClassLoader
- 嵌在JVM內(nèi)核中,用C/C++語言編寫;
- 加載路徑:JAVA_HOME/lib/;
- 不繼承自ClassLoader,沒有父加載器。
(2)自定義類加載器 User-DefinedClassLoader
<2.1>擴展類加載器 ExtClassLoader
- Java編寫,負責加載Java的擴展類庫;
- 加載路徑:JAVA_HOME/jre/lib/ext/。
- 派生自ClassLoader,父加載器為BootstrapClassLoader;
- 繼承關系:ExtClassLoader->URLClassLoader->SecureClassLoader->ClassLoader
<2.2>應用程序類加載器 AppClassLoader
- 加載路徑:CLASSPATH下我們自己寫的類。
- 派生自ClassLoader,父加載器為ExtClassLoader;
- 繼承關系:AppClassLoader->URLClassLoader->SecureClassLoader->ClassLoader
<2.3>自定義類加載器
- 用戶自己定義的類加載器
2、雙親委派
- 類加載器收到了類加載請求,并不會自己先去加載,而是把這個請求委托給父加載器去執(zhí)行;
- 如果父加載器還存在其父加載器,則進一步向上委托,依次遞歸,請求最終到達頂層的啟動類加載器;
- 如果父加載器可以完成類加載任務,就成功返回;若父類加載器無法完成加載任務,子加載器才會嘗試自己去加載。
3、android的類加載器
(1)BootClassLoader
- ClassLoader的子類
- 加載FrameWork層的代碼
- 沒有父加載器
(2)BaseDexClassLoader
- ClassLoader的子類
- 實現(xiàn)了ClassLoader的大部分功能
(3)DexClassLoader
- BaseDexClassLoader的子類
- 加載/data/app目錄下的dex文件
(4)PathClassLoader
- BaseDexClassLoader的子類
- 加載指定目錄的dex文件、包含dex的apk文件、jar文件等。
(5)類加載流程
由于DexClassLoader、PathClassLoader繼承自BaseDexClassLoader,而BaseDexClassLoader繼承自ClassLoader,當我們調(diào)用loadClass時,會調(diào)用到如下代碼:
public abstract class ClassLoader {
static private class SystemClassLoader {
public static ClassLoader loader = ClassLoader.createSystemClassLoader();
}
private static ClassLoader createSystemClassLoader() {
String classPath = System.getProperty("java.class.path", ".");
String librarySearchPath = System.getProperty("java.library.path", "");
// 創(chuàng)建PathClassLoader
return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());
}
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
// 首先,檢查是否已經(jīng)加載過
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
// 然后,如果有父加載器,調(diào)用父加載器的loadClass
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.
// 最后,如果還未找到,調(diào)用findClass加載
c = findClass(name);
}
}
return c;
}
}
- 先查詢自己是否加載過
- 如果有父加載器,通過父加載器加載
- 如果父加載器沒找到,通過findClass加載
上述代碼可以看到,創(chuàng)建PathClassLoader傳入的parent是BootClassLoader 。
class BootClassLoader extends ClassLoader {
protected Class<?> loadClass(String className, boolean resolve)
throws ClassNotFoundException {
Class<?> clazz = findLoadedClass(className);
if (clazz == null) {
clazz = findClass(className);
}
return clazz;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
return Class.classForName(name, false, null);
}
}
如果BootClassLoader沒加載到class,則會回到PathClassLoader的代碼中執(zhí)行,執(zhí)行到它的findClass方法。在findClass中,會通過DexPathList查找class。如果最終未找到,會報ClassNotFoundException異常。
public class BaseDexClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
// 通過pathList尋找class
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException(
"Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}
}
final class DexPathList {
private Element[] dexElements;
public Class<?> findClass(String name, List<Throwable> suppressed) {
// 遍歷element,尋找class
for (Element element : dexElements) {
Class<?> clazz = element.findClass(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
}
4、熱修復原理
上文已經(jīng)知道Android的類加載流程,可以看到會通過BaseDexClassLoader.pathList來尋找class,而DexPathList又是通過字段dexElements進行查找,熱修復就是通過反射拿到DexPathList,生成新的Element數(shù)據(jù),將他的dexElements替換掉從而達成修復的目的。