class加载过程
1.class加載過程
java虛擬機把描述類的數據從class文件加載到內存,并對數據進行 校驗/準備/解析和初始化,最終形成可以被虛擬機直接使用的Java類型,這個過程被稱作虛擬機的類加載機制。
稱作虛擬機的類加載機制。
loading -> linking (verification -> preparation -> resolution)-> resolution)->initializing
loading: 把class文件load到內存中,采用雙親委派,主要是為了安全性
verification: 校驗class文件是否符合標準
preparation: 靜態變量分配內存并設初始值的階段(不包括實例變量)
resolution:把符號引用轉換為直接引用
initializing:靜態變量賦初始值
1.1 Loading 過程
遇到new、getstatic、putstatic或者invokestatic這四條指令時,如果類型沒有進行初始,則需要先觸發其初始化階段。
能夠生成這四條指令的典型Java代碼場景有:
1.使用new關鍵字實例化對象。
2.讀取或設置一個類型的靜態字段。(被final修飾、已在編譯器把結果放入常量池的靜態字段除外,但是final static int i=10; 如果沒有=10還是會在準備階段給它賦默認值,在初始化的時候賦值。)
3.調用一個類型的靜態方法。
4.使用java.lang.reflect包的方法對類型進行反射調用。
5.虛擬機啟動的時候被執行的主類必須初始化。
6.初始化子類時,父類首先初始化。
1.2 Linking過程
Verification:驗證文件是否符合JVM規定.
Preparation: 靜態成員變量賦默認值。
Resolution:將類、方法、屬性等符號引用解析為直接引用常量池中的各種符號引用解析為指針、偏移量等內存地址的直接應用
1.3 Initializing
調用類初始化代碼,給靜態成員變量賦初始值。
1.4 雙親委派模型
雙親委派模型的工作過程是:如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每一個層次的類加載器都是如此,因此所有的加載 請求最終都應該傳送到最頂層的啟動類加載器中,只有當父加載器反饋自己無法完成這個加載請求(它的搜索范圍中沒有找到所需的類)時,子加載器才會嘗試自己去完成加載。
2.類加載器
類加載器實現了loading這個動作,把class文件加載到內存。
1.啟動類加載器(BootstrapClassLoader): 負責加載存放在lib/rt.jar charset.jar等目錄下的核心類,由c++實現。啟動類加載器無法被Java程序直接引用(classLoader的loadClass方法中,若parent為null則使用啟動類加載器.
2.擴展類加載器(ExtensionClassLoader): 負責加載lib/ext目錄下的類,在類sun.misc.Launcher ExtClassLoader中以Java代碼的形式實現。
3.應用程序類加載器(ApplicationClassLoader):負責加載用戶類路徑(ClassPath)下所有的類庫。由sun.misc.LauncherAppClassLoader實現,是ClassLoader類中的getSystemClassLoader()方法的返回值。如果應用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認的類加載器。
4、自定義類加載器:CustomClassLoader
public class ClassLoaderScope {public static void main(String[] args) {System.out.println("-----------------Bootstrap加載類---------");String property = System.getProperty("sun.boot.class.path");String s = property.replaceAll(";",System.lineSeparator());System.out.println(s);System.out.println("-----------------Ext加載類---------------");String property1 = System.getProperty("java.ext.dirs");String s1 = property1.replaceAll(";", System.lineSeparator());System.out.println(s1);System.out.println("-----------------App加載類---------------");String property2 = System.getProperty("java.class.path");String s2 = property2.replaceAll(";", System.lineSeparator());System.out.println(s2);} }2.1 自定義類加載器
只需繼承ClassLoader抽象類,并重寫findClass方法(如果要打破雙親委派模型,需要重寫loadClass方法)原因可以查看ClassLoader的源碼:
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {//First, check if the class has already been loadedClass<?> 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 trrown 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 statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;} }這個是ClassLoader中的loadClass方法,大致流程如下:
1)檢查類是否已加載,如果是則不用再重新加載了;
2)如果未加載,則通過父類加載(依次遞歸)或者啟動類加載器(bootstrap)加載;
3)如果還未找到,則調用本加載器的findClass方法;
以上可知,類加載器先通過父類加載,父類未找到時,才由本加載器加載。
?
因為自定義類加載器是繼承ClassLoader,而我們再看findClass方法:
protected Class<?> findClass(String name) throws ClassNotFoundException {throws new ClassNotFoundException(name); }可以看出,它直接返回ClassNotFoundException。
因此,自定義類加載器必須重寫findClass方法。
自定義類加載器示例代碼:類加載器HClassLoader:
class HClassLoader extends ClassLoader {private String classPath;public HClassLoader(String classPath) {this.classPath = classPath;}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {try {byte[] data = loadByte(name);return defineClass(name, data, 0, data.length);} catch (Exception e) {e.printStackTrace();throw new CLassNotFoundException();}}/*** 獲取.class的字節流* @param name* @return* @throws Exception*/private byte[] loadByte(String name) throws Exception {name = name.replaceAll("\\.", "/");FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");int len = fis.available();byte[] data = new byte[len];fis.read(data);fis.close();// 字節流解密data = DESInstance.deCode("1234567890qwertyuiopasdf".getBytes(), data);return data;} }加載一個類Car:
public class Car {public Car() {System.out.println("Car:" + getClass().getClassLoader());System.out.println("Car Parent:" + getClass().getClassLoader().getParent());}public String print() {System.out.println("Car:print()");return "carPrint";} }測試代碼
@Test public void testClassLoader() throws Exception {HClassLoader myClassLoader = new HClassLoader("e:/temp/a");Class clazz = myClassLoader.loadClass("com.ha.Car");Object o = clazz.newInstance();Method print = clazz.getDeclaredMethod("print", null);print.invoke(o, null); }需要注意的是:
執行測試代碼前,必須將Car.class文件移動到e:/temp/a下,并且按包名建立層級目錄(這里為com/ha/)。因為如果不移動Car.class文件,那么Car類會被AppClassLoader加載(自定義類加載器的parent是AppClassLoader)。
2.2 自定義類加載器的應用
上面介紹了Java類加載器的相關知識。對于自定義類加載器,哪里可以用到呢?
主流的Java Web服務器,比如Tomcat,都實現了自定義的類加載器。因為它要解決幾個問題:
1) Tomcat上可以部署多個不同的應用,但是它們可以使用同一份類庫的不同版本。這就需要自定義類加載器,以便對加載的類庫進行隔離,否則會出現問題;
2)對于非.class的文件,需要轉為Java類,就需要自定義類加載器。比如JSP文件。
這里舉一個其他的例子:Java核心代碼的加密。
假設我們項目當中,有一些核心代碼不像讓別人反編譯看到。當前知道有兩種方法,一種是通過代碼混淆(推薦Allatori,商用收費);一種是自己編寫加密算法,對字節碼加密,加大反編譯難度。
代碼混淆如果用Allatori,比較簡便。注意控制自己編寫類的訪問權限即可。接口用public,內部方法用private,其他的用默認的(即不加訪問修飾符)或者protected。代碼混淆這里不過多說明,這里主要介紹一下字節碼加密。大概流程是:
.class加密代碼:
@Test public void testEncode() {String classFile = "e:/temp/a/com/ha/Car.class";FileInputStream fis = null;try {fis = new FileInputStream(classFile);int len = fis.available();byte[] data = new byte[len];fis.read(data);fis.close();data = DESInstance.enCode("1234567890qwertyuiopasdf".getBytes(), data);String outFile = "e:/temp/a/com/ha/EnCar.class";FileOutputStream fos = new FileOutputStream(outFile);fos.write(data);fos.close();} catch (Exception e) {e.printStackTrace();} }類加載器中解密,查看上文中的:
// 字節流解密 data = DESInstance.deCode("1234567890qwertyuiopasdf".getBytes(), data);加解密工具類
public class DESInstance {private static String ALGORITHM = "DESede";/*** 加密** @param key* @param src* @return*/public static byte[] enCode(byte[] key, byte[] src) {byte[] value = null;SecretKey deskey = new SecretKeySpec(key, ALGORITHM);try {Cipher cipher = Cipher.getInstance(ALGORITHM);cipher.init(Cipher.ENCRYPT_MODE, deskey);value = cipher.doFinal(src);} catch (Exception e) {e.printStackTrace();}return value;}/*** 解密** @param key* @param src* @return*/public static byte[] deCode(byte[] key, byte[] src) {byte[] value = null;SecretKey deskey = new SecretKeyspec(key, ALGORITHM);trye {Cipher cipher = Cipher.getInstance(ALGORITHM);cipher.init(Cipher.DECRYPT_MODE, deskey);value = cipher.doFinal(src);} catch (Exception e) {e.printStackTrace();}return value;} }注意秘鑰是24位,否則會報錯:
java.security.InvalidKeyException: Invalid key length
如果解密密碼錯誤,則是如下錯誤:
javax.crypto.BadPaddingException: Given final block not properly padded
當然,這樣做還是會被反編譯破解,要加大難度,還需要其他處理的。
總結
- 上一篇: HashMap的7种遍历方式
- 下一篇: git clone 获取指定分支的指定c