類的生命周期:
- 加載(Loading):找Class文件
- 驗證(Verification):驗證格式螺戳,依賴
- 準備(Preparation):靜態(tài)字段醋旦,方法表
- 解析(Resolution):符號解析為引用
- 初始化(Initialization):構(gòu)造器、靜態(tài)變量賦值咒劲、靜態(tài)代碼塊
- 使用(Using)
- 卸載(Unloading)
類的初始化時機
- 當虛擬機啟動時顷蟆,初始化用戶指定的主類,就是啟動執(zhí)行的 main 方法所在的類;
- 當遇到用以新建目標類實例的 new 指令時缎患,初始化 new 指令的目標類慕的,就是 new 一個類的時候要初始化;
- 當遇到調(diào)用靜態(tài)方法的指令時,初始化該靜態(tài)方法所在的類;
- 當遇到訪問靜態(tài)字段的指令時挤渔,初始化該靜態(tài)字段所在的類;
- 子類的初始化會觸發(fā)父類的初始化;
- 如果一個接口定義了 default 方法,那么直接實現(xiàn)或者間接實現(xiàn)該接口的類的初始化风题, 會觸發(fā)該接口的初始化;
- 使用反射 API 對某個類進行反射調(diào)用時判导,初始化這個類,其實跟前面一樣沛硅,反射調(diào)用 要么是已經(jīng)有實例了眼刃,要么是靜態(tài)方法,都需要初始化;
- 當初次調(diào)用 MethodHandle 實例時摇肌,初始化該 MethodHandle 指向的方法所在的 類擂红。
MethodHandle 是什么?https://blog.csdn.net/ShuSheng0007/article/details/107066856
不會初始化(可能會加載)
- 通過子類引用父類的靜態(tài)字段围小,只會觸發(fā)父類的初始化昵骤,而不會觸發(fā)子類的初始化树碱。
- 定義對象數(shù)組,不會觸發(fā)該類的初始化变秦。
- 常量在編譯期間會存入調(diào)用類的常量池中成榜,本質(zhì)上并沒有直接引用定義常量的類,不 會觸發(fā)定義常量所在的類蹦玫。
- 通過類名獲取 Class 對象赎婚,不會觸發(fā)類的初始化,Hello.class 不會讓 Hello 類初始 化樱溉。
- 通過 Class.forName 加載指定類時挣输,如果指定參數(shù) initialize 為 false 時,也不會觸 發(fā)類初始化福贞,其實這個參數(shù)是告訴虛擬機歧焦,是否要對類進行初始化。Class.forName (“jvm.Hello”)默認會加載 Hello 類肚医。
- 通過 ClassLoader 默認的 loadClass 方法绢馍,也不會觸發(fā)初始化動作(加載了,但是 不初始化)肠套。
類加載器:
類加載器三大特點:
- 雙親委派模型:如果一個類加載器收到了類加載的請求舰涌,首先不會自己去嘗試加載這個類,而是把這個請求委派給雙親加載器去完成你稚,每一個層次的類加載器都是如此瓷耙。只有當父類加載器反饋自己無法完成這個加載請求時(它的搜索范圍內(nèi)沒有找到所需的類時),子加載器才會嘗試自己完成加載刁赖。
- 負責依賴:加載類的同時要將他依賴的類搁痛,父類,接口等也要加載
- 緩存加載:類加載器加載過一次宇弛,便會緩存下來鸡典,之后可以直接獲取到
如何顯示當前ClassLoader加載了哪些jar包?
public class JvmClassLoaderPrintPath {
public static void main(String[] args) {
URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
System.out.println("啟動類加載器");
for (URL url : urls) {
System.out.println(" --> " + url.toExternalForm());
}
printClassLoader("擴展類加載器", JvmClassLoaderPrintPath.class.getClassLoader().getParent());
printClassLoader("應用類加載器", JvmClassLoaderPrintPath.class.getClassLoader());
}
public static void printClassLoader(String name, ClassLoader CL) {
if (CL != null) {
System.out.println(name + " ClassLoader -> " + CL.toString());
printUrlForClassLoader(CL);
} else {
System.out.println(name + " ClassLoader -> null");
}
}
public static void printUrlForClassLoader(ClassLoader CL) {
Object ucp = insightField(CL, "ucp");
Object path = insightField(ucp, "path");
ArrayList ps = (ArrayList) path;
for (Object p : ps) {
System.out.println(" --> " + p.toString());
}
}
private static Object insightField(Object obj, String fName) {
try{
Field f = null;
if (obj instanceof URLClassLoader) {
f = URLClassLoader.class.getDeclaredField(fName);
} else {
f = obj.getClass().getDeclaredField(fName);
}
f.setAccessible(true);
return f.get(obj);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
添加引用類的幾種方式:
1枪芒、放到 JDK 的 lib/ext 下彻况,或者-Djava.ext.dirs
2、 java –cp/classpath 或者 class 文件放到當前路徑
3舅踪、自定義 ClassLoader 加載
4纽甘、拿到當前執(zhí)行類的 ClassLoader,反射調(diào)用 addUrl 方法添加 Jar 或路徑(JDK9 無效)抽碌。