要點(diǎn):
1庸疾、類(lèi)加載機(jī)制的原理
2蛛株、程序初始化的順序
3砰诵、類(lèi)加載的代理模式(雙親委托機(jī)制)
一摊欠、類(lèi)加載機(jī)制
JVM把class文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)俱箱、準(zhǔn)備国瓮、解析、初始化,最終形成JVM可以直接使用的Java類(lèi)型的過(guò)程乃摹〕Р疲‘
1、加載
將class字節(jié)碼文件加載到內(nèi)存中峡懈,并將這些數(shù)據(jù)轉(zhuǎn)換成方法區(qū)中的運(yùn)行時(shí)數(shù)據(jù)(靜態(tài)變量璃饱、靜態(tài)代碼塊、常量池等)肪康,在堆中生成一個(gè)Class類(lèi)對(duì)象代表這個(gè)類(lèi)(反射原理)荚恶,作為方法區(qū)類(lèi)數(shù)據(jù)的訪(fǎng)問(wèn)入口。
2磷支、鏈接
將Java類(lèi)的二進(jìn)制代碼合并到JVM的運(yùn)行狀態(tài)之中谒撼。
? 驗(yàn)證
確保加載的類(lèi)信息符合JVM規(guī)范,沒(méi)有安全方面的問(wèn)題雾狈。
? 準(zhǔn)備
正式為類(lèi)變量(static變量)分配內(nèi)存并設(shè)置類(lèi)變量初始值的階段廓潜,這些內(nèi)存都將在方法區(qū)中進(jìn)行分配。注意此時(shí)的設(shè)置初始值為默認(rèn)值善榛,具體賦值在初始化階段完成辩蛋。
? 解析
虛擬機(jī)常量池內(nèi)的符號(hào)引用替換為直接引用(地址引用)的過(guò)程。
3移盆、初始化
初始化階段是執(zhí)行類(lèi)構(gòu)造器<clinit>()方法的過(guò)程悼院。類(lèi)構(gòu)造器<clinit>()方法是由編譯器自動(dòng)收集類(lèi)中的所有類(lèi)變量的賦值動(dòng)作和靜態(tài)語(yǔ)句塊(static塊)中的語(yǔ)句合并產(chǎn)生的。
- 當(dāng)初始化一個(gè)類(lèi)的時(shí)候咒循,如果發(fā)現(xiàn)其父類(lèi)還沒(méi)有進(jìn)行過(guò)初始化据途、則需要先初始化其父類(lèi)。
- 虛擬機(jī)會(huì)保證一個(gè)類(lèi)的<clinit>()方法在多線(xiàn)程環(huán)境中被正確加鎖和同步叙甸。
二颖医、Java程序初始化順序
1、父類(lèi)的靜態(tài)變量
2裆蒸、父類(lèi)的靜態(tài)代碼塊
3熔萧、子類(lèi)的靜態(tài)變量
4、子類(lèi)的靜態(tài)代碼塊
5光戈、父類(lèi)的非靜態(tài)變量
6哪痰、父類(lèi)的非靜態(tài)代碼塊
7、父類(lèi)的構(gòu)造方法
8久妆、子類(lèi)的非靜態(tài)變量
9、子類(lèi)的非靜態(tài)代碼塊
10跷睦、子類(lèi)的構(gòu)造方法
/**
* @ClassName Demo01
* @Description 測(cè)試程序初始化順序
* @Author xwd
* @Date 2018/10/23 21:50
*/
public class Demo01 {
public static void main(String[] args) {
B b = new B();
}
}
class A{
static String str1 = "父類(lèi)A的靜態(tài)變量";
String str2 = "父類(lèi)A的非靜態(tài)變量";
static {
System.out.println("執(zhí)行了父類(lèi)A的靜態(tài)代碼塊");
}
{
System.out.println("執(zhí)行了父類(lèi)A的非靜態(tài)代碼塊");
}
public A(){
System.out.println("執(zhí)行了父類(lèi)A的構(gòu)造方法");
}
}
class B extends A{
static String str1 = "子類(lèi)B的靜態(tài)變量";
String str2 = "子類(lèi)B的非靜態(tài)變量";
static {
System.out.println("執(zhí)行了子類(lèi)B的靜態(tài)代碼塊");
}
{
System.out.println("執(zhí)行了子類(lèi)B的非靜態(tài)代碼塊");
}
public B(){
System.out.println("執(zhí)行了子類(lèi)B的構(gòu)造方法");
}
}
控制臺(tái)輸出:
三筷弦、類(lèi)的引用
1、主動(dòng)引用(一定會(huì)初始化)
- new一個(gè)類(lèi)的對(duì)象。
- 調(diào)用類(lèi)的靜態(tài)成員(除了final常量)和靜態(tài)方法烂琴。
- 使用java.lang.reflect包的方法對(duì)類(lèi)進(jìn)行反射調(diào)用爹殊。
- 當(dāng)虛擬機(jī)啟動(dòng),java Hello奸绷,則一定會(huì)初始化Hello類(lèi)梗夸。說(shuō)白了就是先啟動(dòng)main方法所在的類(lèi)。
- 當(dāng)初始化一個(gè)類(lèi)号醉,如果其父類(lèi)沒(méi)有被初始化反症,則先會(huì)初始化他的父類(lèi)
2、被動(dòng)引用
- 當(dāng)訪(fǎng)問(wèn)一個(gè)靜態(tài)域時(shí)畔派,只有真正聲明這個(gè)域的類(lèi)才會(huì)被初始化铅碍。例如:通過(guò)子類(lèi)引用父類(lèi)的靜態(tài)變量,不會(huì)導(dǎo)致子類(lèi)初始化线椰。
- 通過(guò)數(shù)組定義類(lèi)引用胞谈,不會(huì)觸發(fā)此類(lèi)的初始化。
- 引用常量不會(huì)觸發(fā)此類(lèi)的初始化(常量在編譯階段就存入調(diào)用類(lèi)的常量池中了)憨愉。
/**
* @ClassName Demo02
* @Description 測(cè)試類(lèi)的引用
* @Author xwd
* @Date 2018/10/23 21:58
*/
public class Demo02 {
public static void main(String[] args) throws ClassNotFoundException {
//主動(dòng)引用:new一個(gè)類(lèi)的對(duì)象
// People people = new People();
//主動(dòng)引用:調(diào)用類(lèi)的靜態(tài)成員(除了final常量)和靜態(tài)方法
// People.getAge();
// System.out.println(People.age);
//主動(dòng)調(diào)用:使用java.lang.reflect包的方法對(duì)類(lèi)進(jìn)行反射調(diào)用
// Class.forName("pri.xiaowd.classloader.People");
//被動(dòng)引用:當(dāng)訪(fǎng)問(wèn)一個(gè)靜態(tài)域時(shí)烦绳,只有真正聲明這個(gè)域的類(lèi)才會(huì)被初始化。
// System.out.println(WhitePeople.age);
//被動(dòng)引用:通過(guò)數(shù)組定義引用配紫,不會(huì)初始化
// People[] people = new People[10];
//被動(dòng)引用:引用常量不會(huì)觸發(fā)此類(lèi)的初始化
System.out.println(People.num);
}
//主動(dòng)調(diào)用:先啟動(dòng)main方法所在的類(lèi)
// static {
// System.out.println("main方法所在的類(lèi)在虛擬機(jī)啟動(dòng)時(shí)就加載");
// }
}
class People{
static int age = 3;
static final int num = 20;
static {
System.out.println("People被初始化了爵嗅!");
}
public People() {
}
public static int getAge() {
return age;
}
public static void setAge(int age) {
People.age = age;
}
}
class WhitePeople extends People{
static {
System.out.println("WhitePeople被初始化了!");
}
}
四笨蚁、類(lèi)加載器的原理
1睹晒、類(lèi)緩存
標(biāo)準(zhǔn)的Java SE類(lèi)加載器可以按要求查找類(lèi),一旦某個(gè)類(lèi)被加載到類(lèi)加載器中括细,它將維持加載(緩存)一段時(shí)間伪很。不過(guò),JVM垃圾收集器可以回收這些Class對(duì)象奋单。
2锉试、類(lèi)加載器的分類(lèi)
引導(dǎo)類(lèi)加載器(bootstrap class loader)
(1)它用來(lái)加載 Java 的核心庫(kù)(JAVA_HOME/jre/lib/rt.jar,sun.boot.class.path路徑下的內(nèi)容),是用原生代碼(C語(yǔ)言)來(lái)實(shí)現(xiàn)的览濒,并不繼承自 java.lang.ClassLoader呆盖。
(2)加載擴(kuò)展類(lèi)和應(yīng)用程序類(lèi)加載器。并指定他們的父類(lèi)加載器贷笛。
擴(kuò)展類(lèi)加載器(extensions class loader)
(1)用來(lái)加載 Java 的擴(kuò)展庫(kù)(JAVA_HOME/jre/ext/*.jar应又,或java.ext.dirs路徑下的內(nèi)容) 。Java 虛擬機(jī)的實(shí)現(xiàn)會(huì)提供一個(gè)擴(kuò)展庫(kù)目錄乏苦。該類(lèi)加載器在此目錄里面查找并加載 Java類(lèi)株扛。
(2)由sun.misc.Launcher$ExtClassLoader實(shí)現(xiàn)尤筐。
應(yīng)用程序類(lèi)加載器(application class loader)
(1)它根據(jù) Java 應(yīng)用的類(lèi)路徑(classpath,java.class.path 路徑下的內(nèi)容)來(lái)加載 Java 類(lèi)洞就。一般來(lái)說(shuō)盆繁,Java 應(yīng)用的類(lèi)都是由它來(lái)完成加載的。
(2)由sun.misc.Launcher$AppClassLoader實(shí)現(xiàn)旬蟋。
自定義類(lèi)加載器
(1)開(kāi)發(fā)人員可以通過(guò)繼承 java.lang.ClassLoader類(lèi)的方式實(shí)現(xiàn)自己的類(lèi)加載器油昂,以滿(mǎn)足一些特殊的需求。
3倾贰、java.class.ClassLoader類(lèi)
(1)作用:
java.lang.ClassLoader類(lèi)的基本職責(zé)就是根據(jù)一個(gè)指定的類(lèi)的名稱(chēng)冕碟,找到或者生成其對(duì)應(yīng)的字節(jié)代碼,然后從這些字節(jié)代碼中定義出一個(gè)Java類(lèi)躁染,即java.lang.Class類(lèi)的一個(gè)實(shí)例鸣哀。
ClassLoader還負(fù)責(zé)加載 Java 應(yīng)用所需的資源,如圖像文件和配置文件等吞彤。
(2)常用方法:getParent() 返回該類(lèi)加載器的父類(lèi)加載器我衬。
loadClass(String name) 加載名稱(chēng)為 name的類(lèi),返回的結(jié)果是java.lang.Class類(lèi)的實(shí)例饰恕。
此方法負(fù)責(zé)加載指定名字的類(lèi)挠羔,首先會(huì)從已加載的類(lèi)中去尋找,如果沒(méi)有找到埋嵌;從parent ClassLoader[ExtClassLoader]中加載破加;如果沒(méi)有加載到,則從Bootstrap ClassLoader中嘗試加載(findBootstrapClassOrNull方法), 如果還是加載失敗雹嗦,則自己加載范舀。如果還不能加載,則拋出異常ClassNotFoundException了罪。findClass(String name) 查找名稱(chēng)為 name的類(lèi)锭环,返回的結(jié)果是java.lang.Class類(lèi)的實(shí)例。
findLoadedClass(String name) 查找名稱(chēng)為 name的已經(jīng)被加載過(guò)的類(lèi)泊藕,返回的結(jié)果是 java.lang.Class類(lèi)的實(shí)例辅辩。
defineClass(String name, byte[] b, int off, int len) 把字節(jié)數(shù)組 b中的內(nèi)容轉(zhuǎn)換成 Java 類(lèi),返回的結(jié)果是java.lang.Class類(lèi)的實(shí)例娃圆。這個(gè)方法被聲明為 final的玫锋。
resolveClass(Class<?> c) 鏈接指定的 Java 類(lèi)。
五讼呢、類(lèi)加載器的代理模式
代理模式即是將指定類(lèi)的加載交給其他的類(lèi)加載器撩鹿。常用雙親委托機(jī)制。
1吝岭、雙親委托機(jī)制
某個(gè)特定的類(lèi)加載器接收到類(lèi)加載的請(qǐng)求時(shí)三痰,會(huì)將加載任務(wù)委托給自己的父類(lèi)吧寺,直到最高級(jí)父類(lèi)引導(dǎo)類(lèi)加載器(bootstrap class loader)窜管,如果父類(lèi)能夠加載就加載散劫,不能加載則返回到子類(lèi)進(jìn)行加載。如果都不能加載則報(bào)錯(cuò)幕帆。ClassNotFoundException
雙親委托機(jī)制是為了保證 Java 核心庫(kù)的類(lèi)型安全获搏。這種機(jī)制保證不會(huì)出現(xiàn)用戶(hù)自己能定義java.lang.Object類(lèi)等的情況。例如失乾,用戶(hù)定義了java.lang.String常熙,那么加載這個(gè)類(lèi)時(shí)最高級(jí)父類(lèi)會(huì)首先加載,發(fā)現(xiàn)核心類(lèi)中也有這個(gè)類(lèi)碱茁,那么就加載了核心類(lèi)庫(kù)裸卫,而自定義的永遠(yuǎn)都不會(huì)加載。
值得注意是纽竣,雙親委托機(jī)制是代理模式的一種墓贿,但并不是所有的類(lèi)加載器都采用雙親委托機(jī)制。在tomcat服務(wù)器類(lèi)加載器也使用代理模式蜓氨,所不同的是它是首先嘗試去加載某個(gè)類(lèi)聋袋,如果找不到再代理給父類(lèi)加載器。這與一般類(lèi)加載器的順序是相反的穴吹。
六幽勒、自定義類(lèi)加載器
自定義類(lèi)加載器的流程
(1)首先檢查請(qǐng)求的類(lèi)型是否已經(jīng)被這個(gè)類(lèi)裝載器裝載到命名空間中了,如果已經(jīng)裝載港令,直接返回啥容;否則轉(zhuǎn)入步驟2。
(2)委派類(lèi)加載請(qǐng)求給父類(lèi)加載器顷霹,如果父類(lèi)加載器能夠完成咪惠,則返回父類(lèi)加載器加載的Class實(shí)例;否則轉(zhuǎn)入步驟3泼返。
(3)調(diào)用本類(lèi)加載器的findClass(…)方法硝逢,試圖獲取對(duì)應(yīng)的字節(jié)碼,如果獲取的到绅喉,則調(diào)用defineClass(…)導(dǎo)入類(lèi)型到方法區(qū)渠鸽;如果獲取不到對(duì)應(yīng)的字節(jié)碼或者其他原因失敗,返回異常給loadClass(…)柴罐, loadClass(…)轉(zhuǎn)拋異常徽缚,終止加載過(guò)程(注意:這里的異常種類(lèi)不止一種)。
- 注意:被兩個(gè)類(lèi)加載器加載的同一個(gè)類(lèi)革屠,JVM認(rèn)為是不相同的類(lèi)凿试。
import java.io.*;
/**
* @ClassName FileSystemClassLoader
* @Description 自定義文件類(lèi)加載器
* @Author xwd
* @Date 2018/10/24 9:23
*/
public class FileSystemClassLoader extends ClassLoader {
private String rootDir;//根目錄
public FileSystemClassLoader(String rootDir) {
this.rootDir = rootDir;
}
/**
* @MethodName findClass
* @Descrition 加載類(lèi)
* @Param [name]
* @return java.lang.Class<?>
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> loadedClass = findLoadedClass(name);//查詢(xún)?cè)擃?lèi)是否已經(jīng)被加載過(guò)
if(loadedClass != null){ //該類(lèi)已經(jīng)被加載過(guò)了,直接返回
return loadedClass;
}else{ //該類(lèi)還沒(méi)有被加載過(guò)
ClassLoader classLoader = this.getParent();//委派給父類(lèi)加載
try {
loadedClass = classLoader.loadClass(name);
} catch (ClassNotFoundException e) {
// e.printStackTrace();
}
if(loadedClass != null){ //父類(lèi)加載成功,返回
return loadedClass;
}else{
byte[] classData = getClassData(name);
if(classData == null){
throw new ClassNotFoundException();
}else{
loadedClass = defineClass(getName(),classData,0,classData.length);
}
}
}
return loadedClass;
}
/**
* @MethodName getClassData
* @Descrition 根據(jù)類(lèi)名獲得對(duì)應(yīng)的字節(jié)數(shù)組
* @Param [name]
* @return byte[]
*/
private byte[] getClassData(String name) {
//pri.xiaowd.test.A --> D:/myjava/pei/xiaowd/test/A.class
String path = rootDir + "/" + name.replace('.','/') + ".class";
// System.out.println(path);
InputStream is = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
is = new FileInputStream(path);
byte[] bytes = new byte[1024];
int temp = 0;
while((temp = is.read(bytes)) != -1){
baos.write(bytes,0,temp);
}
return baos.toByteArray();
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
} catch (IOException e) {
e.printStackTrace();
return null;
} finally {
try {
if(baos != null){
baos.close();
}
if(is != null){
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
七排宰、線(xiàn)程上下文類(lèi)加載器
通常當(dāng)你需要?jiǎng)討B(tài)加載資源的時(shí)候 , 你至少有三個(gè) ClassLoader 可以選擇 :
1.系統(tǒng)類(lèi)加載器或叫作應(yīng)用類(lèi)加載器 (system classloader or application classloader)
2.當(dāng)前類(lèi)加載器
3.當(dāng)前線(xiàn)程類(lèi)加載器
? 當(dāng)前線(xiàn)程類(lèi)加載器是為了拋棄雙親委派加載鏈模式。
每個(gè)線(xiàn)程都有一個(gè)關(guān)聯(lián)的上下文類(lèi)加載器那婉。如果你使用new Thread()方式生成新的線(xiàn)程板甘,新線(xiàn)程將繼承其父線(xiàn)程的上下文類(lèi)加載器。如果程序?qū)€(xiàn)程上下文類(lèi)加載器沒(méi)有任何改動(dòng)的話(huà)详炬,程序中所有的線(xiàn)程將都使用系統(tǒng)類(lèi)加載器作為上
下文類(lèi)加載器盐类。
? Thread.currentThread().getContextClassLoader()
八、Tomcat服務(wù)器的類(lèi)加載器
每個(gè) Web 應(yīng)用都有一個(gè)對(duì)應(yīng)的類(lèi)加載器實(shí)例呛谜。該類(lèi)加載器也使用代理模式(不同于前面說(shuō)的雙親委托機(jī)制)在跳,所不同的是它是首先嘗試去加載某個(gè)類(lèi),如果找不到再代理給父類(lèi)加載器隐岛。這與一般類(lèi)加載器的順序是相反的猫妙。但也是為了保證安全,這樣核心庫(kù)就不在查詢(xún)范圍之內(nèi)聚凹。