class加載過(guò)程
點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”
優(yōu)質(zhì)文章,第一時(shí)間送達(dá)
76套java從入門(mén)到精通實(shí)戰(zhàn)課程分享
1.class加載過(guò)程
java虛擬機(jī)把描述類(lèi)的數(shù)據(jù)從class文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行 校驗(yàn)/準(zhǔn)備/解析和初始化,最終形成可以被虛擬機(jī)直接使用的Java類(lèi)型,這個(gè)過(guò)程被稱(chēng)作虛擬機(jī)的類(lèi)加載機(jī)制。
loading -> linking (verification-> preparation -> resolution )-> initializing
loading:把class文件load到內(nèi)存中,采用雙親委派,主要是為了安全性
verification:校驗(yàn)class文件是否符合標(biāo)準(zhǔn)
preparation:靜態(tài)變量分配內(nèi)存并設(shè)初始值的階段(不包括實(shí)例變量)
resolution:把符號(hào)引用轉(zhuǎn)換為直接引用
initializing:靜態(tài)變量賦初始值

1.1 Loading 過(guò)程
遇到new,getstatic,putstatic或者invokestatic 這四條指令時(shí),如果類(lèi)型沒(méi)有進(jìn)行初始,則需要先觸發(fā)其初始化階段。
能夠生成這四條指令的典型Java代碼場(chǎng)景有:
1.使用new關(guān)鍵字實(shí)例化對(duì)象。
2.讀取或設(shè)置一個(gè)類(lèi)型的靜態(tài)字段。(被final修飾、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外,但是final static int i=10;如果沒(méi)有=10還是會(huì)在準(zhǔn)備階段給它賦默認(rèn)值,在初始化的時(shí)候賦值。)
3.調(diào)用一個(gè)類(lèi)型的靜態(tài)方法。
4.使用java.lang.reflect包的方法對(duì)類(lèi)型進(jìn)行反射調(diào)用。
5.虛擬機(jī)啟動(dòng)的時(shí)候被執(zhí)行的主類(lèi)必須初始化。
6.初始化子類(lèi)時(shí),父類(lèi)首先初始化。
1.2 Linking 過(guò)程
Verification:驗(yàn)證文件是否符合JVM規(guī)定
Preparation: 靜態(tài)成員變量賦默認(rèn)值
Resolution:將類(lèi)、方法、屬性等符號(hào)引用解析為直接引用常量池中的各種符號(hào)引用解析為指針、偏移量等內(nèi)存地址的直接引用
1.3 Initializing
調(diào)用類(lèi)初始化代碼 ,給靜態(tài)成員變量賦初始值
1.4 雙親委派模型
雙親委派模型的工作過(guò)程是:如果一個(gè)類(lèi)加載器收到了類(lèi)加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載這 個(gè)類(lèi),而是把這個(gè)請(qǐng)求委派給父類(lèi)加載器去完成,每一個(gè)層次的類(lèi)加載器都是如此,因此所有的加載 請(qǐng)求最終都應(yīng)該傳送到最頂層的啟動(dòng)類(lèi)加載器中,只有當(dāng)父加載器反饋?zhàn)约簾o(wú)法完成這個(gè)加載請(qǐng)求 (它的搜索范圍中沒(méi)有找到所需的類(lèi))時(shí),子加載器才會(huì)嘗試自己去完成加載。

2.類(lèi)加載器
類(lèi)加載器實(shí)現(xiàn)了loading這個(gè)動(dòng)作,把class文件加載到內(nèi)存。
1.啟動(dòng)類(lèi)加載器(BootstrapClassLoader):負(fù)責(zé)加載存放在lib/rt.jar charset.jar 等目錄下的核心類(lèi),由c++實(shí)現(xiàn)。啟動(dòng)類(lèi)加載器無(wú)法被Java程序直接引用(classLoader的loadClass方法中,若parent為 null則使用啟動(dòng)類(lèi)加載器。
2.擴(kuò)展類(lèi)加載器(ExtensionClassLoader):負(fù)責(zé)加載lib/ext目錄下的類(lèi),在類(lèi)sun.misc.Launcher E x t C l a s s L o a d e r 中 以 J a v a 代 碼 的 形 式 實(shí) 現(xiàn) 。3. 應(yīng) 用 程 序 類(lèi) 加 載 器 ( A p p l i c a t i o n C l a s s L o a d e r ) :負(fù) 責(zé) 加 載 用 戶(hù) 類(lèi) 路 徑 ( C l a s s P a t h ) 下 所 有 的 類(lèi) 庫(kù) 。由 s u n . m i s c . L a u n c h e r ExtClassLoader 中以Java代碼的形式實(shí)現(xiàn) 。3.應(yīng)用程序類(lèi)加載器(ApplicationClassLoader):負(fù)責(zé)加載用戶(hù)類(lèi)路徑(ClassPath)下所有的類(lèi)庫(kù)。由sun.misc.Launcher ExtClassLoader中以Java代碼的形式實(shí)現(xiàn)。3.應(yīng)用程序類(lèi)加載器(ApplicationClassLoader):負(fù)責(zé)加載用戶(hù)類(lèi)路徑(ClassPath)下所有的類(lèi)庫(kù)。由sun.misc.LauncherAppClassLoader實(shí)現(xiàn) ,是ClassLoader類(lèi)中的getSystemClassLoader()方法的返回值。如果應(yīng)用程序中沒(méi)有自定義過(guò)自己的類(lèi)加載器,一般情況下這個(gè)就是程序中默認(rèn)的類(lèi)加載器。
4、自定義類(lèi)加載器:CustomClassLoader
public class ClassLoaderScope {
public static void main(String[] args) {
System.out.println("-------------------Bootstrap加載類(lèi)-----------------")
String property = System.getProperty("sun.boot.class.path");
String s = property.replaceAll(";", System.lineSeparator());
System.out.println(s);
System.out.println("-------------------Ext加載類(lèi)-------------------");
String property1 = System.getProperty("java.ext.dirs");
String s1 = property1.replaceAll(";", System.lineSeparator());
System.out.println(s1);
System.out.println("-------------------App加載類(lèi)-------------------");
String property2 = System.getProperty("java.class.path");
String s2 = property2.replaceAll(";", System.lineSeparator());
System.out.println(s2);
}
}
2.1 自定義類(lèi)加載器
只需繼承ClassLoader抽象類(lèi),并重寫(xiě)findClass方法(如果要打破雙親委派模型,需要重寫(xiě)loadClass方法)原因可以查看ClassLoader的源碼:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> 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 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.
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;
}
}
這個(gè)是ClassLoader中的loadClass方法,大致流程如下:
1)檢查類(lèi)是否已加載,如果是則不用再重新加載了;
2)如果未加載,則通過(guò)父類(lèi)加載(依次遞歸)或者啟動(dòng)類(lèi)加載器(bootstrap)加載;
3)如果還未找到,則調(diào)用本加載器的findClass方法;
以上可知,類(lèi)加載器先通過(guò)父類(lèi)加載,父類(lèi)未找到時(shí),才有本加載器加載。
因?yàn)樽远x類(lèi)加載器是繼承ClassLoader,而我們?cè)倏磃indClass方法:
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
可以看出,它直接返回ClassNotFoundException。
因此,自定義類(lèi)加載器必須重寫(xiě)findClass方法。
自定義類(lèi)加載器示例代碼:類(lèi)加載器HClassLoader:
class HClassLoader extends ClassLoader {
private String classPath;
public HClassLoader(String classPath) {
this.classPath = classPath;
}
@Override
protected 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的字節(jié)流
* @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();
// 字節(jié)流解密
data = DESInstance.deCode("1234567890qwertyuiopasdf".getBytes(), data);
return data;
}
}
加載一個(gè)類(lèi)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";
}
}
測(cè)試代碼
@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);
}
需要注意的是:
執(zhí)行測(cè)試代碼前,必須將Car.class文件移動(dòng)到e:/temp/a下,并且按包名建立層級(jí)目錄(這里為com/ha/)。因?yàn)槿绻灰苿?dòng)Car.class文件,那么Car類(lèi)會(huì)被AppClassLoader加載(自定義類(lèi)加載器的parent是AppClassLoader)。
2.2 自定義類(lèi)加載器的應(yīng)用
上面介紹了Java類(lèi)加載器的相關(guān)知識(shí)。對(duì)于自定義類(lèi)加載器,哪里可以用到呢?
主流的Java Web服務(wù)器,比如Tomcat,都實(shí)現(xiàn)了自定義的類(lèi)加載器。因?yàn)樗鉀Q幾個(gè)問(wèn)題:
1)Tomcat上可以部署多個(gè)不同的應(yīng)用,但是它們可以使用同一份類(lèi)庫(kù)的不同版本。這就需要自定義類(lèi)加載器,以便對(duì)加載的類(lèi)庫(kù)進(jìn)行隔離,否則會(huì)出現(xiàn)問(wèn)題;
2)對(duì)于非.class的文件,需要轉(zhuǎn)為Java類(lèi),就需要自定義類(lèi)加載器。比如JSP文件。
這里舉一個(gè)其它的例子:Java核心代碼的加密。
假設(shè)我們項(xiàng)目當(dāng)中,有一些核心代碼不想讓別人反編譯看到。當(dāng)前知道有兩種方法,一種是通過(guò)代碼混淆(推薦Allatori,商用收費(fèi));一種是自己編寫(xiě)加密算法,對(duì)字節(jié)碼加密,加大反編譯難度。
代碼混淆如果用Allatori,比較簡(jiǎn)便。注意控制自己編寫(xiě)類(lèi)的訪問(wèn)權(quán)限即可。接口用public,內(nèi)部方法用private,其他的用默認(rèn)的(即不加訪問(wèn)修飾符)或者protected。代碼混淆這里不過(guò)多說(shuō)明,這里主要介紹一下字節(jié)碼加密。
大概流程是:

.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();
}
}
類(lèi)加載器中解密,查看上文中的:
// 字節(jié)流解密
data = DESInstance.deCode("1234567890qwertyuiopasdf".getBytes(), data);
加解密工具類(lèi)
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);
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, deskey);
value = cipher.doFinal(src);
} catch (Exception e) {
e.printStackTrace();
}
return value;
}
}
注意秘鑰是24位,否則會(huì)報(bào)錯(cuò):
java.security.InvalidKeyException: Invalid key length
如果解密密碼錯(cuò)誤,則是如下錯(cuò)誤:
javax.crypto.BadPaddingException: Given final block not properly padded
當(dāng)然,這樣做還是會(huì)被反編譯破解,要加大難度,還需要其他處理的。
————————————————
版權(quán)聲明:本文為CSDN博主「兢兢業(yè)業(yè)的子牙」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明。
原文鏈接:
https://blog.csdn.net/qq_33449307/article/details/114950029
鋒哥最新SpringCloud分布式電商秒殺課程發(fā)布
??????
??長(zhǎng)按上方微信二維碼 2 秒
感謝點(diǎn)贊支持下哈 
