JAVA提高第七篇 類加載器解析
今天我們學(xué)習(xí)類加載器,關(guān)于類加載器其實(shí)和JVM有很大關(guān)系,在這里這篇文章只是簡(jiǎn)單的介紹下類加載器,后面學(xué)習(xí)到JVM的時(shí)候還會(huì)詳細(xì)講到類加載器,本文分為下面幾個(gè)小節(jié)講解:
一、認(rèn)識(shí)類加載器
1.什么是類加載器?
所謂的類加載器可以從其作用來(lái)理解,其功能就是將classpath目錄下.class文件,加載到內(nèi)存中來(lái)進(jìn)行一些處理,處理完的結(jié)果就是一些字節(jié)碼.那是誰(shuí)把這些class類加載到內(nèi)存中來(lái)的呢?就是類加載器。
2.JVM中默認(rèn)的類加載器有哪些?
java虛擬機(jī)中可以安裝多個(gè)類加載器,系統(tǒng)默認(rèn)三個(gè)主要的類加載器,每個(gè)類加載器負(fù)責(zé)加載不同位置的類:BootStrap,ExtClassLoader,AppClassLoader
注意的是:
1.類加載器本身也是一個(gè)java類,因?yàn)轭惣虞d器本身也是一個(gè)java類,那么這個(gè)特殊的java類【類加載器】是有誰(shuí)加載進(jìn)來(lái)的呢?這顯然要有第一個(gè)類加載器,這第一個(gè)類加載器不是一個(gè)java類,它是BootStrap。
2.BootStrap不是一個(gè)java類,不需要類加載器java加載,他是嵌套在java虛擬機(jī)內(nèi)核里面的。java 虛擬機(jī)內(nèi)核已啟動(dòng)的時(shí)候,他就已經(jīng)在那里面了,他是用c++語(yǔ)言寫(xiě)的一段二進(jìn)制代碼。他可以去加載別的類,其中別的類就包含了類加載器【如上面提到的Ext 和 app】。
案例:
下面我們寫(xiě)個(gè)例子來(lái)獲取ClassLoaderTest這個(gè)類的類加載器的名字,代碼如下:
package study.javaenhance;
import java.util.ArrayList;
public class ClassLoaderTest
{
public static void main(String[] args) throws Exception
{
//獲取類加載器,那么這個(gè)獲取的是一個(gè)實(shí)例對(duì)象,我們知道類加載器也有很多種,那么因此也有其對(duì)應(yīng)的類存在,因此可以獲取到對(duì)應(yīng)的字節(jié)碼
System.out.println(ClassLoaderTest.class.getClassLoader());
//獲取類加載的字節(jié)碼,然后獲取到類加載字節(jié)碼的名字
System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());
//下面我們看下獲取非我們定義的類,比如System ArrayList 等常用類
System.out.println(System.class.getClassLoader());
System.out.println(ArrayList.class.getClassLoader());
}
}
結(jié)果如下:
sun.misc.Launcher$AppClassLoader@1c78e57
sun.misc.Launcher$AppClassLoader
null
null
結(jié)果分析:
ClassLoaderTest的類加載器的名稱是AppClassLoader。也就是這個(gè)類是由AppClassLoader這個(gè)類加載器加載的。
System/ArrayList的類加載器是null。這說(shuō)明這個(gè)類加載器是由BootStrap加載的。因?yàn)槲覀兩厦嬲f(shuō)了BootStrap不是java類,不需要類加載器加載。所以他的類加載器是null。
==================================
我們說(shuō)了java給我們提供了三種類加載器:BootStrap,ExtClassLoader,AppClassLoader。這三種類加載器是有父子關(guān)系組成了一個(gè)樹(shù)形結(jié)構(gòu)。BootStrap是根節(jié)點(diǎn),BootStrap下面掛著ExtClassLoader,ExtClassLoader下面掛著AppClassLoader.
代碼演示如下:
package study.javaenhance;
import java.util.ArrayList;
public class ClassLoaderTest
{
public static void main(String[] args) throws Exception
{
//獲取類加載器,那么這個(gè)獲取的是一個(gè)實(shí)例對(duì)象,我們知道類加載器也有很多種,那么因此也有其對(duì)應(yīng)的類存在,因此可以獲取到對(duì)應(yīng)的字節(jié)碼
System.out.println(ClassLoaderTest.class.getClassLoader());
//獲取類加載的字節(jié)碼,然后獲取到類加載字節(jié)碼的名字
System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());
//下面我們看下獲取非我們定義的類,比如System ArrayList 等常用類
System.out.println(System.class.getClassLoader());
System.out.println(ArrayList.class.getClassLoader());
//演示java 提供的類加載器關(guān)系
ClassLoader classloader = ClassLoaderTest.class.getClassLoader();
while(classloader != null)
{
System.out.print(classloader.getClass().getName()+"-->");
classloader = classloader.getParent();
}
System.out.println(classloader);
}
}
輸出結(jié)果為:
sun.misc.Launcher$AppClassLoader-->sun.misc.Launcher$ExtClassLoader-->null
通過(guò)這段程序可以看出來(lái),ClassLoaderTest由AppClassLoader加載,AppClassLoader的父類節(jié)點(diǎn)是ExtClassLoader,ExtClassLoader的父節(jié)點(diǎn)是BootStrap。

每一個(gè)類加載器都有自己的管轄范圍。 BootStrap根節(jié)點(diǎn),只負(fù)責(zé)加載rt.jar里的類,剛剛那個(gè)System就是屬于rt.jar包里面的,ExtClassLoader負(fù)責(zé)加載JRE/lib/ext/*.jar這個(gè)目錄文件夾下的文件。而AppClassLoader負(fù)責(zé)加載ClassPath目錄下的所有jar文件及目錄。
最后一級(jí)是我們自定義的加載器,他們的父類都是AppClassLoader。
二、類加載器的雙親委派機(jī)制
除了系統(tǒng)自帶了類加載器,我們還可以自定義類加載器。然后把自己的類加載器掛在樹(shù)上。作為某個(gè)類加載器的孩子。所有自定義類加載器都要繼承ClassLoader。實(shí)現(xiàn)里面的一個(gè)方法ClassLoader()如下:

通過(guò)上面的知識(shí),我們知道java提供了三個(gè)類加載器,而且我們也可以自定義類加載器,并且通過(guò)上面的類加載圖也看到了之前的關(guān)系,那么對(duì)于一個(gè)類的.class 到底是誰(shuí)去加載呢?
當(dāng)Java虛擬機(jī)要加載第一個(gè)類的時(shí)候,到底派出哪個(gè)類加載器去加載呢?
(1). 首先當(dāng)前線程的類加載器去加載線程中的第一個(gè)類(當(dāng)前線程的類加載器:Thread類中有一個(gè)get/setContextClassLoader(ClassLoader cl);方法,可以獲取/指定本線程中的類加載器)
(2). 如果類A中引用了類B,Java虛擬機(jī)將使用加載類A的類加載器來(lái)加載類B
(3). 還可以直接調(diào)用ClassLoader.loadClass(String className)方法來(lái)指定某個(gè)類加載器去加載某個(gè)類
每個(gè)類加載器加載類時(shí),又先委托給其上級(jí)類加載器當(dāng)所有祖宗類加載器沒(méi)有加載到類,回到發(fā)起者類加載器,還加載不了,則會(huì)拋出ClassNotFoundException,不是再去找發(fā)起者類加載器的兒子,因?yàn)闆](méi)有g(shù)etChild()方法。例如:如上圖所示: MyClassLoader->AppClassLoader->Ext->ClassLoader->BootStrap.自定定義的MyClassLoader1首先會(huì)先委托給AppClassLoader,AppClassLoader會(huì)委托給ExtClassLoader,ExtClassLoader會(huì)委托給BootStrap,這時(shí)候BootStrap就去加載,如果加載成功,就結(jié)束了。如果加載失敗,就交給ExtClassLoader去加載,如果ExtClassLoader加載成功了,就結(jié)束了,如果加載失敗就交給AppClassLoader加載,如果加載成功,就結(jié)束了,如果加載失敗,就交給自定義的MyClassLoader1類加載器加載,如果加載失敗,就報(bào)ClassNotFoundException異常,結(jié)束。
這樣的好處在哪里呢?可以集中管理,不會(huì)出現(xiàn)多份字節(jié)碼重復(fù)的現(xiàn)象。有兩個(gè)類要再在System,如果讓底層的類加載器加載,可能會(huì)出現(xiàn)兩份字節(jié)碼。而都讓爺爺加載,爺爺加載到已有,當(dāng)再有請(qǐng)求過(guò)來(lái)的時(shí)候,爺爺說(shuō):哎,我加載過(guò)啊,直接把那份拿出來(lái)給你用啊。就不會(huì)出現(xiàn)多份字節(jié)碼重復(fù)的現(xiàn)象。
現(xiàn)在有一道面試題:能不能自己寫(xiě)一套java.lang.System.?
分析:你寫(xiě)了也白寫(xiě),因?yàn)轭惣虞d器加載,直接到爺爺那里去找,找成功了,分本就不回來(lái)理你的那個(gè)。
答案:通常不可以,因?yàn)槲袡C(jī)制委托給爺爺,爺爺在rt.jar包加載到這個(gè)類以后就不會(huì)加載你自己寫(xiě)了那個(gè)System類了。但是,我也有辦法加載,我寫(xiě)一個(gè)自己的類加載器,不讓他用委托機(jī)制,不委托給上級(jí)了,就可以了.
因?yàn)镾ystem類,List,Map等這樣的系統(tǒng)提供jar類都在rt.jar中,所以由BootStrap類加載器加載,因?yàn)锽ootStrap是祖先類,不是Java編寫(xiě)的,所以打印出class為null
對(duì)于ClassLoaderTest類的加載過(guò)程,打印結(jié)果也是很清楚的。
三、自定義類加載器
下面來(lái)看一下怎么定義我們自己的一個(gè)類加載器MyClassLoader:
自定義的類加載器必須繼承抽象類ClassLoader然后重寫(xiě)findClass方法,其實(shí)他內(nèi)部還有一個(gè)loadClass方法和defineClass方法,這兩個(gè)方法的作用是:
loadClass方法的源代碼:
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
再來(lái)看一下loadClass(name,false)方法的源代碼:
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{
//加上鎖,同步處理,因?yàn)榭赡苁嵌嗑€程在加載類
synchronized (getClassLoadingLock(name)) {
//檢查,是否該類已經(jīng)加載過(guò)了,如果加載過(guò)了,就不加載了
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//如果自定義的類加載器的parent不為null,就調(diào)用parent的loadClass進(jìn)行加載類
if (parent != null) {
c = parent.loadClass(name, false);
} else {
//如果自定義的類加載器的parent為null,就調(diào)用findBootstrapClass方法查找類,就是Bootstrap類加載器
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();
//如果parent加載類失敗,就調(diào)用自己的findClass方法進(jìn)行類加載
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;
}
}
在loadClass代碼中也可以看到類加載機(jī)制的原理,這里還有這個(gè)方法findBootstrapClassOrNull,看一下源代碼:
private Class findBootstrapClassOrNull(String name)
{
if (!checkName(name)) return null;
return findBootstrapClass(name);
}
就是檢查一下name是否是否正確,然后調(diào)用findBootstrapClass方法,但是findBootstrapClass方法是個(gè)native本地方法,看不到源代碼了,但是可以猜測(cè)是用Bootstrap類加載器進(jìn)行加載類的,這個(gè)方法我們也不能重寫(xiě),因?yàn)槿绻貙?xiě)了這個(gè)方法的話,就會(huì)破壞這種委托機(jī)制,我們還要自己寫(xiě)一個(gè)委托機(jī)制。
defineClass這個(gè)方法很簡(jiǎn)單就是將class文件的字節(jié)數(shù)組編程一個(gè)class對(duì)象,這個(gè)方法肯定不能重寫(xiě),內(nèi)部實(shí)現(xiàn)是在C/C++代碼中實(shí)現(xiàn)的findClass這個(gè)方法就是根據(jù)name來(lái)查找到class文件,在loadClass方法中用到,所以我們只能重寫(xiě)這個(gè)方法了,只要在這個(gè)方法中找到class文件,再將它用defineClass方法返回一個(gè)Class對(duì)象即可。
這三個(gè)方法的執(zhí)行流程是:每個(gè)類加載器:loadClass->findClass->defineClass
前期的知識(shí)了解后現(xiàn)在就來(lái)實(shí)現(xiàn)了
首先來(lái)看一下需要加載的一個(gè)類:ClassLoaderAttachment.java:
package study.javaenhance;
public class ClassLoaderAttachment {
@Override
public String toString() {
return "Hello ClassLoader!";
}
}
這個(gè)類中輸出一段話即可:編譯成ClassLoaderAttachment.class
再來(lái)看一下自定義的MyClassLoader.java:
package study.javaenhance;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class MyClassLoader extends ClassLoader
{
//需要加載類.class文件的目錄
private String classDir;
//無(wú)參的構(gòu)造方法,用于class.newInstance()構(gòu)造對(duì)象使用
public MyClassLoader(){
}
public MyClassLoader(String classDir){
this.classDir = classDir;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
System.out.println(name);
String classPathFile = classDir + "\\" + name.substring(name.lastIndexOf(".")+1) + ".class";
System.out.println(classPathFile);
try
{
System.out.println("my");
//將class文件進(jìn)行解密
FileInputStream fis = new FileInputStream(classPathFile);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
encodeAndDecode(fis,bos);
byte[] classByte = bos.toByteArray();
//將字節(jié)流變成一個(gè)class
return defineClass(classByte,0,classByte.length);
} catch (Exception e)
{
e.printStackTrace();
}
return super.findClass(name);
}
//測(cè)試,先將ClassLoaderAttachment.class文件加密寫(xiě)到工程的class_temp目錄下
public static void main(String[] args) throws Exception{
//配置運(yùn)行參數(shù)
String srcPath = args[0];//ClassLoaderAttachment.class原路徑
String desPath = args[1];//ClassLoaderAttachment.class輸出的路徑
String desFileName = srcPath.substring(srcPath.lastIndexOf("\\")+1);
String desPathFile = desPath + "/" + desFileName;
FileInputStream fis = new FileInputStream(srcPath);
FileOutputStream fos = new FileOutputStream(desPathFile);
//將class進(jìn)行加密
encodeAndDecode(fis,fos);
fis.close();
fos.close();
}
/**
* 加密和解密算法
* @param is
* @param os
* @throws Exception
*/
private static void encodeAndDecode(InputStream is,OutputStream os) throws Exception{
int bytes = -1;
while((bytes = is.read())!= -1){
bytes = bytes ^ 0xff;//和0xff進(jìn)行異或處理
os.write(bytes);
}
}
}
這個(gè)類中定義了一個(gè)加密和解密的算法,很簡(jiǎn)單的,就是將字節(jié)和oxff異或一下即可,而且這個(gè)算法是加密和解密的都可以用!
當(dāng)然我們還要先做一個(gè)操作就是,將ClassLoaderAttachment.class加密后的文件存起來(lái),也就是在main方法中執(zhí)行的,這里我是在項(xiàng)目中新建一個(gè)

同時(shí)采用的是參數(shù)的形式來(lái)進(jìn)行賦值的,所以在運(yùn)行的MyClassLoader的時(shí)候要進(jìn)行輸入?yún)?shù)的配置:右擊MyClassLoader->run as -> run configurations

第一個(gè)參數(shù)是ClassLoaderAttachment.class文件的源路徑,第二個(gè)參數(shù)是加密后存放的目錄,運(yùn)行MyClassLoader之后,刷新class_temp文件夾,出現(xiàn)了ClassLoaderAttachment.class,這個(gè)是加密后的class文件。
下面來(lái)看一下測(cè)試類:
package study.javaenhance;
import java.util.ArrayList;
public class ClassLoaderTest
{
public static void main(String[] args) throws Exception
{
//獲取類加載器,那么這個(gè)獲取的是一個(gè)實(shí)例對(duì)象,我們知道類加載器也有很多種,那么因此也有其對(duì)應(yīng)的類存在,因此可以獲取到對(duì)應(yīng)的字節(jié)碼
System.out.println(ClassLoaderTest.class.getClassLoader());
//獲取類加載的字節(jié)碼,然后獲取到類加載字節(jié)碼的名字
System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());
//下面我們看下獲取非我們定義的類,比如System ArrayList 等常用類
System.out.println(System.class.getClassLoader());
System.out.println(ArrayList.class.getClassLoader());
//演示java 提供的類加載器關(guān)系
ClassLoader classloader = ClassLoaderTest.class.getClassLoader();
while(classloader != null)
{
System.out.print(classloader.getClass().getName()+"-->");
classloader = classloader.getParent();
}
System.out.println(classloader);
try {
//Class classDate = new MyClassLoader("class_temp").loadClass("ClassLoaderAttachment");
Class classDate = new MyClassLoader("class_temp").loadClass("study.javaenhance.ClassLoaderAttachment");
Object object = classDate.newInstance();
//輸出ClassLoaderAttachment類的加載器名稱
System.out.println("ClassLoader:"+object.getClass().getClassLoader().getClass().getName());
System.out.println(object);
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
結(jié)果如下:
sun.misc.Launcher$AppClassLoader@6b97fd
sun.misc.Launcher$AppClassLoader
null
null
sun.misc.Launcher$AppClassLoader-->sun.misc.Launcher$ExtClassLoader-->null
ClassLoader:sun.misc.Launcher$AppClassLoader
Hello ClassLoader!
這個(gè)時(shí)候我們會(huì)發(fā)現(xiàn)調(diào)用的APP 的類加載器然后輸出了結(jié)果,這個(gè)是正常的,因?yàn)檫@個(gè)時(shí)候會(huì)采用雙親委派機(jī)制。
那么這個(gè)時(shí)候,我們將自己生成的ClassLoaderAttachemet class文件,覆蓋掉編譯的時(shí)候生成的class 文件看下結(jié)果如何,如果正常應(yīng)該會(huì)報(bào)錯(cuò),因?yàn)檫@個(gè)時(shí)候走雙親委派機(jī)制在對(duì)應(yīng)的classpath 是可以找到這個(gè)class 文件,因此APP類加載器會(huì)處理,但是因?yàn)槲覀兊腸lass 是加密的因此會(huì)報(bào)錯(cuò),運(yùn)行結(jié)果如:

那么如何讓其走到我們自定義的類加載器呢,只需要將編譯時(shí)候生成的目錄下的.class 文件刪掉即可,那么這個(gè)是APP加載不到,則會(huì)去調(diào)用findclass ,然后就會(huì)走到我們定義的類加載器中,運(yùn)行結(jié)果如下:

參考資料:
張孝祥老師java增強(qiáng)視頻
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
AsyncHttpClient?ChannelPool線程池頻道池源碼流程解析
這篇文章主要為大家介紹了AsyncHttpClient ChannelPool線程池頻道池源碼流程解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12
使用原生JDBC動(dòng)態(tài)解析并獲取表格列名和數(shù)據(jù)的方法
這篇文章主要介紹了使用原生JDBC動(dòng)態(tài)解析并獲取表格列名和數(shù)據(jù),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-08-08
Spring使用AspectJ注解和XML配置實(shí)現(xiàn)AOP
這篇文章主要介紹了Spring使用AspectJ注解和XML配置實(shí)現(xiàn)AOP的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10
Springboot自定義注解&傳參&簡(jiǎn)單應(yīng)用方式
SpringBoot框架中,通過(guò)自定義注解結(jié)合AOP可以實(shí)現(xiàn)功能如日志記錄與耗時(shí)統(tǒng)計(jì),首先創(chuàng)建LogController和TimeConsuming注解,并為L(zhǎng)ogController定義參數(shù),然后,在目標(biāo)方法上應(yīng)用這些注解,最后,使用AspectJ的AOP功能,通過(guò)切點(diǎn)表達(dá)式定位這些注解2024-10-10

