編譯原理請(qǐng)查看之前內(nèi)容滚秩,.java文件編譯過(guò)程和執(zhí)行過(guò)程分析以及計(jì)算機(jī)簡(jiǎn)單認(rèn)識(shí)
需要了解更多Java原創(chuàng)資料,請(qǐng)加qq:1811112688淮捆,或者加老薛的微信郁油,lukun0402。
本內(nèi)容全部原創(chuàng)攀痊,純手敲桐腌,對(duì)你有幫助可以點(diǎn)贊、關(guān)注哦9毒丁Aú簟!轉(zhuǎn)載請(qǐng)注明出處:http://www.reibang.com/p/deb040582060
1 類加載器
1:編譯涩笤、加載過(guò)程
2:java虛擬機(jī)何時(shí)會(huì)結(jié)束什么周期嚼吞?
i:正常執(zhí)行完畢程序
ii:執(zhí)行了System.exit();
iii:程序執(zhí)行期間發(fā)生了異常或者錯(cuò)誤而終止
iv:由于底層操作系統(tǒng)出錯(cuò)蹬碧,導(dǎo)致虛擬機(jī)結(jié)束進(jìn)程
3:ClassLoader將數(shù)據(jù)加載到內(nèi)存中經(jīng)過(guò)的步驟:
i舱禽、加載:加載類的二進(jìn)制數(shù)據(jù)
ii、鏈接
.驗(yàn)證 確保加載的類的正確性恩沽。
.準(zhǔn)備 類中的靜態(tài)變量分配內(nèi)存誊稚,并且其初始化為默認(rèn)值。
.解析 把類中的符號(hào)引用變?yōu)橹苯右谩?iii、初始化為類中的類中的靜態(tài)變量賦值(正確的初始值)
3-1:驗(yàn)證:
類文件的結(jié)構(gòu)檢查:
確保類文件遵從Java類文件的固定格式(防止偽造字節(jié)碼文件)
語(yǔ)義檢查:
確保類本身符合Java語(yǔ)言的語(yǔ)法定義里伯,這一步在編譯器也會(huì)操作城瞎。比如在java規(guī)范中方法的重寫和void無(wú)關(guān)的,但是在虛擬機(jī)規(guī)范中疾瓮,重寫的定義和Java規(guī)范中的重寫定義不同脖镀。在虛擬機(jī)規(guī)范中方法返回值+方法簽名構(gòu)成了一個(gè)方法的重寫定義。這一部分有可能會(huì)被惡意用戶利用狼电。
字節(jié)碼驗(yàn)證:
確保字節(jié)碼可以被Java虛擬機(jī)安全的執(zhí)行蜒灰。一條指令包含操作碼+一個(gè)或多個(gè)操作數(shù)。例如:invokespecial #1
二進(jìn)制兼容性驗(yàn)證
確保引用的類之間協(xié)調(diào)一致肩碟。比如某個(gè)類A中引用了其他類B的的f();方法强窖,name在執(zhí)行A類的時(shí)候會(huì)檢查B類中是否存在f();方法,如果不存在削祈?就會(huì)拋出NoSuchMethodException翅溺。
PS:這里不存在的原因是由于:我們都知道如果出現(xiàn)這種情況下,編譯期間就會(huì)報(bào)錯(cuò)髓抑,但是如果別人惡意篡改了呢未巫?又或者A類和B類的編譯時(shí)通過(guò)不同的版本編譯的,那么就會(huì)出現(xiàn)不兼容情況启昧。
3-2:準(zhǔn)備:
準(zhǔn)備比階段Java虛擬機(jī)會(huì)為了的靜態(tài)變量分配內(nèi)存叙凡,并設(shè)置默認(rèn)的初始值。例如對(duì)于Person類密末,在準(zhǔn)備階段握爷。會(huì)給Person類中的int類型的靜態(tài)變量num分配4個(gè)字節(jié)的內(nèi)存空間,并且賦值默認(rèn)值0严里。
class Person{
static int num = 10;//在準(zhǔn)備階段時(shí)新啼,num在內(nèi)存中的值為0
}
3-3:解析:
在解析階段,Java虛擬機(jī)會(huì)把類的二進(jìn)制數(shù)據(jù)中的符號(hào)引用換為直接引用刹碾。例如:
class A{
B b = new B();
void method(){
b.fun();//這段代碼在A類的二進(jìn)制數(shù)據(jù)中其實(shí)就是符號(hào)引用
}
}
class B{
void fun(){
}
}
PS:其實(shí)符號(hào)引用為了方便程序開發(fā)燥撞。程序員能夠看懂在什么情況下,調(diào)用了那個(gè)方法而已迷帜,但是在計(jì)算機(jī)執(zhí)行的過(guò)程中物舒,真正能夠讓計(jì)算機(jī)讀懂的是二進(jìn)制數(shù)據(jù)。在二進(jìn)制數(shù)據(jù)中戏锹,Java虛擬機(jī)會(huì)將符號(hào)引用替換為一個(gè)指針冠胯,這個(gè)指針指向了方法該方法的棧幀地址,由fun方法的全名和相關(guān)描述符組成锦针。</pre>
3-4:初始化:
在初始化階段Java虛擬機(jī)會(huì)為類的靜態(tài)變量賦予初始值荠察。靜態(tài)變量初始化分為兩種:
在靜態(tài)變量聲明處進(jìn)行初始化:
public class A{
public static int num1 = 10置蜀;//準(zhǔn)備階段num值為0,初始化階段值為 10
public static double num2;//準(zhǔn)備階段num值為0.0悉盆,初始化階段值為0.0
}
在靜態(tài)代碼塊中進(jìn)行初始化:
public class A{
public static double num2;//準(zhǔn)備階段num值為0.0
static{
num2 = 12;//初始化階段值為12.0
}
}
3-5:初始化的面試題
public class Test02 {//啟動(dòng)器類 會(huì)被加載
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();//調(diào)用靜態(tài)方法 類被加載
System.out.println(s1.count1);
System.out.println(s1.count2);
}
}
class Singleton{
/ /1:準(zhǔn)備階段各個(gè)變量的值分別為null盯荤,0,0
private static Singleton singleton = new Singleton();//2:初始化階段 開始賦值
public static int count1;//5:count1變?yōu)?
public static int count2 = 0;//6:count2重新賦值0
private Singleton(){
count1++;//3:初始化階段變成1
count2++;//4:初始化階段變成1
}
public static Singleton getInstance(){
return singleton;
}
}
public class Test02 {//啟動(dòng)器類 會(huì)被加載
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();//調(diào)用靜態(tài)方法 類被加載
System.out.println(s1.count1);
System.out.println(s1.count2);
}
}
class Singleton{
//1:準(zhǔn)備階段各個(gè)變量的值分別為0焕盟,0秋秤,null,
public static int count1;//2:初始化階段 開始賦值 0
public static int count2 = 0;//3:初始化階段 開始賦值 0
private static Singleton singleton = new Singleton();//4:初始化階段 開始賦值
private Singleton(){
count1++;//5:初始化階段變成1
count2++;//6:初始化階段變成1
}
public static Singleton getInstance(){
return singleton;
}
}
4:類的初始化步驟:
類如果沒有被加載和連接京髓,先加載和連接
類存在直接父類航缀,且這個(gè)類沒有被初始化商架,那就先初始化直接父類
類中若存在初始化語(yǔ)句堰怨,依次執(zhí)行初始化語(yǔ)句
5:如何確定一個(gè)類被加載了?
Java程序?qū)τ陬惖氖褂梅譃閮蓚€(gè)部分:
i:主動(dòng)使用
A:創(chuàng)建一類的實(shí)例 new Person();
B*:訪問(wèn)某個(gè)類蛇摸、接口中的靜態(tài)變量备图,或者對(duì)于靜態(tài)變量做讀寫;
C:調(diào)用類中的靜態(tài)方法赶袄;
D:反射 (Class.forName("xxx.xxx.xxx"));
E*:初始化一個(gè)類的子類的揽涮,當(dāng)前類加載。
F:Java虛擬機(jī)標(biāo)明的啟動(dòng)器類 (Test.class(main)|Person.class|Student.class饿肺,此時(shí)Test就是啟動(dòng)器類).
ii:被動(dòng)使用
a:引用常量不會(huì)導(dǎo)致該類發(fā)生初始化[常量它是在編譯器確定的]
b: 通過(guò)數(shù)組定義類的引用蒋困,不會(huì)觸發(fā)該類的初始化
c:通過(guò)子類引用父類的靜態(tài)變量,不會(huì)導(dǎo)致子類初始化敬辣。
剩余的都是被動(dòng)使用雪标。。溉跃。
PS:注意村刨,java程序首次通過(guò)主動(dòng)使用時(shí),系統(tǒng)會(huì)初始化該類或者接口撰茎。
5-1: 創(chuàng)建實(shí)例
public class Tes01{
public static void main(String[] args){
new A();//此時(shí)會(huì)導(dǎo)致A類會(huì)被加載
}
}
class A{
}
5-2: 訪問(wèn)某個(gè)類的靜態(tài)變量
public class Tes01{
public static void main(String[] args){
System.out.println(A.num);//此時(shí)會(huì)導(dǎo)致A類會(huì)被加載
}
}
class A{
static{
System.out.println("我被初始化了嵌牺。。");
}
static int num = 10;
}
5-2-1:特例:
public class Tes01{
public static void main(String[] args){
System.out.println(A.num);//此時(shí)會(huì)導(dǎo)致A類初始化
}
}
class A{
static{
System.out.println("我被初始化了龄糊。逆粹。");
}
static final int num = new Random().nextInt(50);//編譯器無(wú)法確定,必須要初始化當(dāng)前A類才能確定值炫惩,所以訪問(wèn)時(shí)A會(huì)被初始化
}
public class Tes01{
public static void main(String[] args){
System.out.println(A.num);//此時(shí)會(huì)不會(huì)導(dǎo)致A類初始化
}
}
class A{
static{
System.out.println("我被初始化了枯饿。。");
}
static final int num = 10/2;//編譯器可以確定值诡必,所以訪問(wèn)時(shí)A不會(huì)被初始化
}
public class Tes01{
public static void main(String[] args){
System.out.println(S.num);//此時(shí)會(huì)不會(huì)導(dǎo)致S類初始化,只會(huì)初始化F類
}
}
class F{
static int num = 10;
static{
System.out.println("F 我被初始化了奢方。搔扁。");
}
}
class S extends F{
static{
System.out.println("S 我被初始化了。蟋字。");
}
}
PS:
1:如果final修飾的靜態(tài)常量可以在編譯期間確定值稿蹲,那么不會(huì)導(dǎo)致當(dāng)前類初始化,如果訪問(wèn)的final修飾的靜態(tài)常量不可以在編譯期間確定值鹊奖,那么會(huì)導(dǎo)致當(dāng)前類初始化苛聘。
2:只有當(dāng)程序訪問(wèn)的靜態(tài)變量或靜態(tài)方法確實(shí)在當(dāng)前類或當(dāng)前接口中定義時(shí),才會(huì)認(rèn)為是對(duì)于當(dāng)前類和接口的主動(dòng)使用忠聚。
5-3:調(diào)用類的靜態(tài)方法
public class Tes01{
public static void main(String[] args){
new A().fun();//此時(shí)會(huì)導(dǎo)致A類會(huì)被加載
}
}
class A{
static void fun(){}
}
5-4:反射
public class Tes01{
public static void main(String[] args){
Class.forName("com.mage.jvm.A");//此時(shí)會(huì)導(dǎo)致A類會(huì)被加載
}
}
class A{
}
5-5:初始化子類
public class Tes01{
public static void main(String[] args){
new B();//此時(shí)會(huì)導(dǎo)致A類會(huì)被加載,包括B類的間接父類也會(huì)被加載
}
}
class A{
}
class B extends A{
}
public class Tes01{
public static void main(String[] args){
new A();//先執(zhí)行a的初始化
new B();//此時(shí)會(huì)導(dǎo)致A類會(huì)被加載,由于已經(jīng)初始化過(guò)了设哗,所以不會(huì)初始化。但是在一個(gè)類加載器的情況下两蟀,如果有其他加載器网梢,還是會(huì)加載的。
}
}
class A{
static{System.out.println("A");}
}
class B extends A{
static{System.out.println("B");}
}
5-6:表明的啟動(dòng)器類
public class Tes01{
public static void main(String[] args){
System.out.println("hello");//執(zhí)行時(shí)會(huì)導(dǎo)致當(dāng)前Test01加載
}
}
5-7:一些特殊的不會(huì)導(dǎo)致加載的情況
5-7-1:初始化父類并不包含接口
public class Test01{
public static void main(String[] args){
new Son();//會(huì)導(dǎo)致Father赂毯、Son加載战虏,但是不會(huì)導(dǎo)致Sun的父接口Run加載;
}
}
class Father{
static{System.out.println("父類加載了");}
}
class Son extends Father{
static{System.out.println("子類加載了");}
}
interface Run{
Thread rThread = new Thread(){
{
System.out.println("run 執(zhí)行了");
}
};
}
5-7-2:某些情境下會(huì)導(dǎo)致父接口初始化
程序首次使用特定接口的靜態(tài)變量時(shí)党涕,會(huì)導(dǎo)致該接口初始化烦感。
public class Test01 {
public static void main(String[] args) {
System.out.println(SRun.sThread);//不會(huì)導(dǎo)致FRun加載
}
}
interface FRun{
int fNum = new Random().nextInt(3);
Thread fThread = new Thread(){
//每個(gè)實(shí)例創(chuàng)建時(shí)都會(huì)執(zhí)行一次
{
System.out.println("FRun invoked"+fNum);
}
};
}
interface SRun extends FRun{
int sNum = new Random().nextInt(3);
Thread sThread = new Thread(){
//每個(gè)實(shí)例創(chuàng)建時(shí)都會(huì)執(zhí)行一次
{
System.out.println("SRun invoked"+sNum);
}
};
}
5-7-3:初始化一個(gè)接口時(shí),并不會(huì)初始化它的父接口
public class Test01 {
public static void main(String[] args) {
System.out.println(SRun.sThread);//不會(huì)導(dǎo)致FRun加載
}
}
interface FRun{
int fNum = new Random().nextInt(3);
Thread fThread = new Thread(){
//每個(gè)實(shí)例創(chuàng)建時(shí)都會(huì)執(zhí)行一次
{
System.out.println("FRun invoked"+fNum);
}
};
}
interface SRun extends FRun{
int sNum = new Random().nextInt(3);
Thread sThread = new Thread(){
//每個(gè)實(shí)例創(chuàng)建時(shí)都會(huì)執(zhí)行一次
{
System.out.println("SRun invoked"+sNum);
System.out.println(fThread);//調(diào)用父類時(shí)膛堤,會(huì)導(dǎo)致父接口加載
}
};
}
5-7-4:被動(dòng)使用不到值初始化
A: 通過(guò)數(shù)組定義類的引用手趣,不會(huì)觸發(fā)該類的初始化。
public class Test01 {
public static void main(String[] args) {
Person[] ps = new Person[10];
}
}
class Person{
static{
System.out.println("被初始化了肥荔。绿渣。。次企。");
}
}
B:通過(guò)子類引用父類的靜態(tài)變量怯晕,不會(huì)導(dǎo)致子類初始化。
public class Test01 {
public static void main(String[] args) {
System.out.println(S.num);
}
}
class P{
static int num = 10;
static{
System.out.println("父類被加載了缸棵。舟茶。。");
}
}
class S extends P{
static{
System.out.println("子類類被加載了堵第。吧凉。。踏志。");
}
}
C:調(diào)用ClassLoader類的loadClass方法加載一個(gè)類阀捅,并不是對(duì)于類的主動(dòng)使用。
package com.mage.test;
public class Test03{
public static void main(String[] args) throws Exception {
F.class.getClassLoader().loadClass("com.mage.test.F");
ClassLoader.getSystemClassLoader().loadClass("com.mage.test.F");
}
}
class F{
static{
System.out.println("F 我被初始化了针余。饲鄙。");
}
}
5:加載.class文件的方式:
-1:本地系統(tǒng)當(dāng)中直接加載 *
-2:通過(guò)網(wǎng)絡(luò)下載.class文件
-3:從jar凄诞、zip等歸檔文件中加載.class文件 *
-4:數(shù)據(jù)庫(kù)(.class文件)
-5:動(dòng)態(tài)編譯.class</pre>
當(dāng)一個(gè)類加載到內(nèi)存中之后,會(huì)在堆內(nèi)存當(dāng)中產(chǎn)生一個(gè)對(duì)應(yīng)的Class的對(duì)象忍级,所有當(dāng)前的類的 實(shí)例以及當(dāng)前該類都共享這一份Class實(shí)例帆谍。
6:類加載器
java虛擬機(jī)自帶的加載器:
根類加載器(bootstrap)
擴(kuò)展類加載器(PlatformClassLoader、低版本 Ext)
系統(tǒng)類加載器(AppClassLoader)
用戶自定義加載器
java.lang.ClassLoader的子類
可以定制類的加載方法
注意:類加載器并不需要等到一個(gè)類"首次主動(dòng)使用"時(shí)在加載它轴咱。 可以預(yù)先加載汛蝙。JVM規(guī)范中定義類,類加載器可以在預(yù)料到某個(gè)類可能 需要加載時(shí)預(yù)先加載朴肺。 hotspot窖剑、jrokit、j9都是對(duì)于JVM規(guī)范的一種實(shí)現(xiàn)戈稿。
ps:注意用戶的自定義類加載的父加載器 不一定全是系統(tǒng)類加載器
6-1:JVM加載器詳解:
根加載器 | 無(wú)父加載器西土,負(fù)責(zé)加載虛擬機(jī)核心類庫(kù)。比如LANG包下的類器瘪。它的實(shí)現(xiàn)以來(lái)與底層操作系統(tǒng)翠储,屬于虛擬機(jī)實(shí)現(xiàn)的一部分绘雁,沒有繼承==JAVA.LANG.CLASSLOADER==類 |
---|---|
擴(kuò)展加載器 | 它的父類是根加載器橡疼,一般加載jre/lib/ext下的目錄。如果將自定義創(chuàng)建好的jar文件放入該目錄庐舟,則jar文件會(huì)自動(dòng)交由擴(kuò)展類加載器加載欣除。擴(kuò)展類加載器是java.lang.ClassLoader的子類。 |
系統(tǒng)類加載器 | 應(yīng)用類加載器挪略,父加載器是擴(kuò)展類加載器历帚。從配置的ClassPath或者系統(tǒng)指定的(一般為當(dāng)前java命令執(zhí)行的當(dāng)前目錄)中加載類。是用戶自定義類加載器的父加載器杠娱。也是java.lang.ClassLoader的子類 |
自定義類加載器 | 用戶自定義類加載器需要繼承java.lang.ClassLoader挽牢。 |
public class Test01 {
public static void main(String[] args) {
String str = "";
//NULL -> 根加載器 bootstrap[c、c++]
System.out.println(str.getClass().getClassLoader());
//jdk.internal.loader.ClassLoaders$AppClassLoader@512ddf17
//應(yīng)用類加載器
System.out.println(new Person().getClass().getClassLoader());
//jdk.internal.loader.ClassLoaders$PlatformClassLoader@2752f6e2
//EXT加載 擴(kuò)展類加載器
System.out.println(new Person().getClass().getClassLoader().getParent());
//根加載器 bootstrap[c摊求、c++]
System.out.println(new Person().getClass().getClassLoader().getParent().getParent());
}
}
class Person{}
6-2:類加載器關(guān)系圖
6-3: 類加載器的雙親委派機(jī)制(父類委托機(jī)制)
類加載器將類加載到j(luò)ava虛擬機(jī)中禽拔。jdk1.2之后,類的加載機(jī)制采用父類委托機(jī)制室叉,可以更好的保證安全性睹栖。除了Java虛擬機(jī)自帶的根加載器以外,其他類加載都有且只有一個(gè)父加載器茧痕。當(dāng)Java程序請(qǐng)求加載loader1加載A類時(shí)野来,loader1首先委派自己的父加載器加載A類,若父加載器可以加載踪旷,交由父加載器完成該加載任務(wù)凿可,否則由loader1加載A類。
PS:各個(gè)加載器按照父子關(guān)系形成一個(gè)樹形結(jié)構(gòu)磨镶,除了根加載器外嗅义,其余加載器都有且包含一個(gè)父加載器。
6-3-1: 樹形結(jié)構(gòu)圖:
ps:執(zhí)行過(guò)程悬包,loader2首先會(huì)在自己的命名空間中查找A類是否被加載,如果已經(jīng)加載了,直接返回代表A類的Class對(duì)象的引用灰追。如果A類沒有被加載,loader2會(huì)請(qǐng)求loader1代為加載狗超,loader1再請(qǐng)求系統(tǒng)類加載器加載弹澎。一直請(qǐng)求到根加載器加載,如果在請(qǐng)求過(guò)程中都不能加載努咐,此時(shí)系統(tǒng)類加載器嘗試加載苦蒿,如果能夠加載成功,則將A對(duì)應(yīng)的Class對(duì)象返回給loader1渗稍,loader1再返回給loader2佩迟,如果系統(tǒng)類加載也不能加載成功,則交由loader1進(jìn)行加載竿屹,loader1如果也失敗报强,則loader2嘗試加載。如果所有的父加載器以及l(fā)oader2本身都沒有加載成功拱燃,則拋出ClassNotFountException異常秉溉。
注意,在這個(gè)過(guò)程中碗誉,如果有一個(gè)類加載器成功加載了A類召嘶,name這個(gè)類加載器稱之為==定義類加載器==,所有能夠成功返回Class對(duì)象的引用的類加載器包括定義類加載器在內(nèi)都稱之為==初始類加載器==哮缺。
例如:如果loader1加載A類成功弄跌,那么loader1稱之為A類的定義類加載器,而loader1和loader2都稱之為A類的初始類加載器尝苇。
6-3-2: 父類委托機(jī)制中的父類含義:
父類委托機(jī)制中的父子關(guān)系大多數(shù)場(chǎng)景下不見得是父子的繼承關(guān)系铛只,大多數(shù)場(chǎng)景下其實(shí)是
組合
的關(guān)系。并且在一些場(chǎng)景下我們還能看到父子加載器是同一個(gè)加載器的兩個(gè)實(shí)例茎匠。比如:
自定義類加載器中同一個(gè)加載器的兩個(gè)實(shí)例格仲,出現(xiàn)父子關(guān)系。
public class Test04 {
//這里只是為了表現(xiàn)一個(gè)加載器的兩個(gè)不同實(shí)例诵冒,也可以出現(xiàn)父子關(guān)系
//本次代碼寫的不作為具體的自定義類加載器
public static void main(String[] args) {
ClassLoader classLoader1 = new MyClassLoader();
ClassLoader classLoader2 = new MyClassLoader(classLoader1);
}
}
class MyClassLoader extends ClassLoader{
public MyClassLoader(){
}
public MyClassLoader(ClassLoader cl){
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
return super.findClass(name);
}
}
Java中ClassLoader構(gòu)造器:
ClassLoader文檔注釋:
CLASSLOADER |
---|
protected ClassLoader() |
Creates a new class loader using the ClassLoader returned by the method getSystemClassLoader() as the parent class loader.If there is a security manager, its checkCreateClassLoader method is invoked. This may result in a security exception. |
Throws:SecurityException -If a security manager exists and its checkCreateClassLoader method doesn't allow creation of a new class loader. |
ps:這里我們能看到通過(guò)ClassLoader構(gòu)造器返回的是通過(guò)getSystemClassLoader()獲取到的加載器作為當(dāng)前加載器的父類凯肋。
ClassLoader源碼解讀:
public abstract class ClassLoader {
protected ClassLoader() {
//一:
this(checkCreateClassLoader(), null, getSystemClassLoader());
}
//三:
private ClassLoader(Void unused, String name, ClassLoader parent)
{
this.name = name;
//四:
this.parent = parent;
this.unnamedModule = new Module(this);
if (ParallelLoaders.isRegistered(this.getClass())) {
parallelLockMap = new ConcurrentHashMap<>();
package2certs = new ConcurrentHashMap<>();
assertionLock = new Object();
} else {
// no finer-grained lock; lock on the classloader instance
parallelLockMap = null;
package2certs = new Hashtable<>();
assertionLock = this;
}
this.nameAndId = nameAndId(this);
}
//二:
public static ClassLoader getSystemClassLoader() {
switch (VM.initLevel()) {
case 0:
case 1:
case 2:
// the system class loader is the built-in app class loader during startup
return getBuiltinAppClassLoader();
case 3:
String msg = "getSystemClassLoader cannot be called during the system class loader instantiation";
throw new IllegalStateException(msg);
default:
// system fully initialized
assert VM.isBooted() && scl != null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkClassLoaderPermission(scl, Reflection.getCallerClass());
}
return scl;
}
}
}
ps: 在源碼中我們看到當(dāng)要構(gòu)建ClassLoader對(duì)象時(shí)[一],需要通過(guò)getSystemClassLoader
方法獲取到一個(gè)ClassLoader對(duì)象[二]汽馋,將獲取到的對(duì)象傳入帶參構(gòu)造器的中[三]侮东,執(zhí)行了this.parent = parent;
[四]圈盔。
總結(jié)一句話,當(dāng)我們創(chuàng)建類加載器對(duì)象時(shí)悄雅,傳入的類加載器A是創(chuàng)建的類加載器B的父加載器驱敲。默認(rèn)情況下創(chuàng)建的類加載器的父加載器是系統(tǒng)類加載器。
return getBuiltinAppClassLoader();
6-3-3:為什么需要設(shè)計(jì)父類委托機(jī)制
6-3-3-1:why? 出于何種考慮設(shè)計(jì)的父類委托機(jī)制宽闲?
父類委托機(jī)制可以提高軟件系統(tǒng)的安全性众眨,由于用戶自定義的類加載器是無(wú)法在父類委托機(jī)制下加載應(yīng)該由父加載器加載的類。防止了不安全甚至是惡意代碼的加載容诬。比如lang包下的類是由根加載器加載娩梨,其他任何用戶自定義加載器都無(wú)法加載包含了惡意代碼的java.lang.Object類。
例子:假設(shè)自己設(shè)計(jì)一個(gè)類览徒,然后是存在惡意代碼的狈定,如果沒有父類委托機(jī)制,通過(guò)自己編寫的加載器习蓬,直接加載到內(nèi)存中纽什,這就有問(wèn)題了。如果存在父類委托機(jī)制躲叼,那么交由父加載器加載芦缰,父加載器按照jvm規(guī)則加載,如果存在惡意代碼押赊,就不會(huì)加載饺藤。
6-3-3-2: how? 如何保證安全包斑?
需要提及兩個(gè)概念流礁,一個(gè)是命名空間,一個(gè)是運(yùn)行時(shí)包罗丰。
命名空間:存在的目的就是為了防止同名神帅。
每個(gè)類加載器都有自己的命名空間,命名空間=該類加載器+所有父加載器所加載的類組成萌抵。
JAVA虛擬機(jī)為每一個(gè)類加載器維護(hù)一個(gè)命名空間找御。在同一個(gè)命名空間下,不會(huì)出現(xiàn)名字完全相同的兩個(gè)類绍填,不同命名空間下霎桅,有可能會(huì)出現(xiàn)相同兩個(gè)類。
一個(gè)類在被類加載器加載到內(nèi)存中之后讨永,是可以被其他類加載器繼續(xù)加載的滔驶。只不過(guò)這兩個(gè)類加載器不能存在父子關(guān)系。如果一個(gè)類在加載到內(nèi)存中之前卿闹,已經(jīng)存在一個(gè)類加載器加載過(guò)了揭糕,那么在相同命名空間下就不會(huì)被加載萝快,
運(yùn)行時(shí)包:同一類加載器加載的屬于相同包的類組成了運(yùn)行時(shí)包。
運(yùn)行時(shí)包 = 包名 + 定義類加載器(第一次加載一個(gè)類成功的加載器稱之為當(dāng)前類的定義類加載器著角,后續(xù)所有的子加載器稱之為該類) 揪漩。
這樣才能防止用戶自定義類冒充核心類庫(kù),調(diào)用一些包可見的方法吏口。
package com.mage.jvm01;
public class Test01 {
public static void main(String[] args) {
//測(cè)試調(diào)用String類下的lastIndexOf方法奄容,該方法是默認(rèn)的訪問(wèn)權(quán)限
byte b = 12;
String.lastIndexOf(new byte[12],b,2,"",1);//編譯報(bào)錯(cuò),由于方法不可訪問(wèn)到
}
}
結(jié)論:不能直接訪問(wèn)默認(rèn)訪問(wèn)權(quán)限的不同包下的類产徊。
package java.lang;
public class System {
public static void main(String[] args) {
byte b = 12;
//冒充為lang包下的類嫩海,訪問(wèn)默認(rèn)修飾符的方法,編譯通過(guò)囚痴,執(zhí)行出錯(cuò)叁怪。
String.lastIndexOf(new byte[12],b,2,"",1);
}
}
ps:由于運(yùn)行時(shí)包不光要看加載類的包名還要看加載該類的類加載器。由于lang下的類和我們自定義的lang包下的類不由同一類加載器加載深滚,所以他們不再一個(gè)包下奕谭。
6-3-3-3: 面試題
能不能自己寫個(gè)類叫java.lang.System
?
答案:通常不可以痴荐,但可以采取另類方法達(dá)到這個(gè)需求血柳。 解釋:為了不讓我們寫System類,類加載采用委托機(jī)制生兆,這樣可以保證爸爸們優(yōu)先难捌,爸爸們能找到的類,兒子就沒有機(jī)會(huì)加載鸦难。而System類是Bootstrap加載器加載的根吁,就算自己重寫,也總是使用Java系統(tǒng)提供的System合蔽,自己寫的System類根本沒有機(jī)會(huì)得到加載击敌。
但是,我們可以自己定義一個(gè)類加載器來(lái)達(dá)到這個(gè)目的拴事,為了避免雙親委托機(jī)制沃斤,這個(gè)類加載器也必須是特殊的。由于系統(tǒng)自帶的三個(gè)類加載器都加載特定目錄下的類刃宵,如果我們自己的類加載器放在一個(gè)特殊的目錄衡瓶,那么系統(tǒng)的加載器就無(wú)法加載,也就是最終還是由我們自己的加載器加載牲证。
6-4:自定義類加載器
6-4-1:自定義類加載器步驟
要?jiǎng)?chuàng)建用戶自定義類加載器哮针,需要繼承java.lang.ClassLoader類,然后重寫一些findClass(String str)方法。這個(gè)方法根據(jù)指定的類的名字诚撵,返回對(duì)應(yīng)的Class對(duì)象的引用缭裆。
源碼解析
第一步:
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
Finds the class with the specified binary name. This method
should be overridden by class loader implementations that follow
the delegation model for loading classes, and will be invoked by
the loadClass method after checking the parent class loader for
the requested class.</pre>
ps:這里findClass只會(huì)拋出一個(gè)異常,我們需要重寫findClass方法寿烟。在官方文檔中的描述澈驼,根據(jù)只指定的binary name(二進(jìn)制名稱)查找類。通過(guò)遵守父類委托機(jī)制的類加載器需要重寫這個(gè)方法區(qū)加載一個(gè)類筛武,在執(zhí)行請(qǐng)求一個(gè)類時(shí)會(huì)先檢查父加載器缝其,然后將執(zhí)行l(wèi)oadClass方法。
第二步:
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
Loads the class with the specified binary name. This
method searches for classes in the same manner as
the loadClass(String, boolean) method. It is invoked by the
Java virtual machine to resolve class references. Invoking
this method is equivalent to invoking loadClass(name, false).
ps:執(zhí)行該方法和執(zhí)行l(wèi)oadClass(String, boolean) 這個(gè)方法的搜索類的方式相同徘六。該方法的執(zhí)行時(shí)通過(guò)jvm區(qū)識(shí)別類的引用内边,和執(zhí)行l(wèi)oadClass(String, boolean)相同。在代碼中我們也發(fā)現(xiàn)其實(shí)底層就是調(diào)用loadClass(String, boolean)待锈。
第三步:
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
PerfCounter.getParentDelegationTime().addTime(t1 - t0); PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
ps:這里我們要關(guān)注幾點(diǎn)漠其,第一點(diǎn)就是當(dāng)前方法首先會(huì)檢查類是否被加載過(guò),如果沒有被加載竿音,會(huì)執(zhí)行parent != null
的判定和屎,這里的parent就是當(dāng)前加載器的父加載器。如果存在父加載器春瞬,就會(huì)交由父加載器加載柴信。如過(guò)是空則交由根加載器加載。
第四步:關(guān)于parent變量的解釋:
protected ClassLoader() {
this(checkCreateClassLoader(), null, getSystemClassLoader());
}
i:默認(rèn)情況下創(chuàng)建ClassLoader時(shí)會(huì)調(diào)用其他構(gòu)造器宽气。且傳入的parent為系統(tǒng)類加載器随常。
private ClassLoader(Void unused, String name, ClassLoader parent) {
this.name = name;
this.parent = parent;//這行代碼很重要
this.unnamedModule = new Module(this);
if (ParallelLoaders.isRegistered(this.getClass())) {
parallelLockMap = new ConcurrentHashMap<>();
package2certs = new ConcurrentHashMap<>();
assertionLock = new Object();
} else {
// no finer-grained lock; lock on the classloader instance
parallelLockMap = null;
package2certs = new Hashtable<>();
assertionLock = this;
}
this.nameAndId = nameAndId(this);
}
ii:這會(huì)將系統(tǒng)類加載作為當(dāng)前類加載器的父類。
編寫自定義類加載器
/**
* 編寫自定義類加載器:
* 1:繼承ClassLoader
* 2:重寫findClass
* 3:通過(guò)自定義的loadClassData萄涯,將字節(jié)碼文件讀取到字節(jié)數(shù)組中
* 4:通過(guò)defineClass方法將字節(jié)數(shù)組中的數(shù)據(jù)轉(zhuǎn)換為Class對(duì)象
* 5:測(cè)試
*/
public class MyClassLoader extends ClassLoader{
//定義加載的目錄:
private String path = "";
//定義加載文件的后綴
private final static String fileType = ".class";
//定義類加載的名稱
private String name ;
public MyClassLoader(String name){
super();
this.name = name;
}
public MyClassLoader(String name,ClassLoader parent){
super(parent);
this.name = name;
}
//重寫findClass
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[ ] data = loadClassData(name);
final Class<?> aClass = defineClass(name, data, 0, data.length);
return aClass;
}
//定義loadClass方法 通過(guò)全限定名稱 獲取字節(jié)數(shù)組
private byte[] loadClassData(String name){
//聲明返回?cái)?shù)據(jù)
byte[ ] data = null;
try(InputStream is = new FileInputStream(new File(path+name+fileType));
ByteArrayOutputStream baos = new ByteArrayOutputStream() ){
int len = 0;
while((len = is.read())!= -1){
baos.write(len);
}
data = baos.toByteArray();
}catch (IOException e){
e.printStackTrace();
}
return data;
}
public void setPath(String path) {
this.path = path;
}
@Override
public String toString() {
return "MyClassLoader{" +
"path='" + path + '\'' +
", name='" + name + '\'' +
'}';
}
public static void main(String[] args) {
MyClassLoader loader1 = new MyClassLoader("loader01");
loader1.setPath("/Users/iongst/app/client/");
MyClassLoader loader2 = new MyClassLoader("loader02",loader1);
loader2.setPath("/Users/iongst/app/serverlib/");
MyClassLoader loader3 = new MyClassLoader("loader03",null);
loader3.setPath("/Users/iongst/app/otherlib/");
loader(loader1);
loader(loader2);
loader(loader3);
}
private static void loader(ClassLoader classLoader) {
try {
Class clz = classLoader.loadClass("A");
Constructor c = clz.getConstructor(null);
c.newInstance(null);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}catch (Exception e){
e.printStackTrace();
}
}
}
自定義類加載器關(guān)系
一個(gè)類被加載多次
A 被加載: jdk.internal.loader.ClassLoaders$AppClassLoader@799f7e29
?B 被加載:jdk.internal.loader.ClassLoaders$AppClassLoader@799f7e29
A 加載:MyClassLoader{path='/Users/iongst/app/otherlib/',name='loader03'}
?B 加載:
MyClassLoader{path='/Users/iongst/app/otherlib/', name='loader03'}
PS:A類和B類被重復(fù)加載多次绪氛。由于選擇的類加載器不同,導(dǎo)致系統(tǒng)類加載器和loader3在各自的命名空間中都存在A窃判、和B類
面試問(wèn)題:
在A類中的B類會(huì)交由那個(gè)類加載器加載呢钞楼?
答案分析:同樣會(huì)交由加載A類的加載器加載B類,同時(shí)在加載B類時(shí)也會(huì)遵守父類委托機(jī)制袄琳。測(cè)試以上代碼,主需要將將在A類的某個(gè)目錄下的B類文件刪除燃乍,查看B類時(shí)由那個(gè)類加載即可唆樊。
當(dāng)前 A 被加載:MyClassLoader{path='/Users/iongst/app/client/', name='loader01'}
?B 被加載:jdk.internal.loader.ClassLoaders$AppClassLoader@799f7e29</pre>
6-4-2 命名空間不同導(dǎo)致的可見性關(guān)系
同一個(gè)命名空間下的類時(shí)互相可見的。
子加載器的命名空間包含的所有父加載器的命名空間刻蟹。因此子加載器記載的類可以看見父加載器加載的類逗旁。例如系統(tǒng)類加載器加載的類時(shí)可以看見根加載器加載的類的。
父加載器加載的類不能看見子加載器所加載的類。
如果兩個(gè)加載器之間沒有之間或間接的父子關(guān)系片效,那么他們各自加載的類互不可見红伦。
測(cè)試用例:
在原來(lái)的代碼的基礎(chǔ)上,保證client存在A和B類淀衣,然后在sys文件夾中運(yùn)行MyClassLoader.查看結(jié)果昙读。
public static void main(String[] args) {
MyClassLoader loader1 = new MyClassLoader("loader01");
loader1.setPath("/Users/iongst/app/client/");
MyClassLoader loader2 = new MyClassLoader("loader02",loader1);
loader2.setPath("/Users/iongst/app/serverlib/");
loader(loader1);
try {
Class clz = loader1.loadClass("A");
Constructor c = clz.getConstructor(null);
Object obj = c.newInstance(null);
A a = (A)obj;
System.out.println(a.num);
} catch (Exception e) {
e.printStackTrace();
}
}
結(jié)果:
A加載:MyClassLoader{path='/Users/iongst/app/client/', name='loader01'}
?B 被加載:MyClassLoader{path='/Users/iongst/app/client/', name='loader01'}
A 被加載:MyClassLoader{path='/Users/iongst/app/client/', name='loader01'}
?B 被加載:MyClassLoader{path='/Users/iongst/app/client/', name='loader01'}
Exception in thread "main" java.lang.NoClassDefFoundError: Aat MyClassLoader.main(MyClassLoader.java:77)
Caused by: java.lang.ClassNotFoundException: A
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassL oader.java:583)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
... 1 more</pre>
原因描述:由于MyClassLoader的類加載器是Sys系統(tǒng)類加載器。而A和B的加載時(shí)通過(guò)loader01加載的膨桥。也就MyClassLoader看不見A類蛮浑。導(dǎo)致java.lang.NoClassDefFoundError。
如果把client中的文件導(dǎo)入到sys中只嚣,就不會(huì)出現(xiàn)問(wèn)題沮稚。
7:類的卸載
7-1:測(cè)試用例
public static void main(String[] args) {
MyClassLoader loader1 = new MyClassLoader("loader01");
loader1.setPath("/Users/iongst/app/client/");
try {
Class clz = loader1.loadClass("A");
System.out.println("class hashcode"+clz.hashCode());
Constructor c = clz.getConstructor(null);
Object obj = c.newInstance(null);
loader1 = null;
clz = null;
obj = null;
loader1 = new MyClassLoader("loader01");
loader1.setPath("/Users/iongst/app/client/");
clz = loader1.loadClass("A");
System.out.println("class hashcode"+clz.hashCode());
c = clz.getConstructor(null);
obj = c.newInstance(null);
} catch (Exception e) {
e.printStackTrace();
}
}
7-2 結(jié)果分析
7-2-1:結(jié)論分析:
A類有l(wèi)oader1加載。在類加載器的內(nèi)部實(shí)現(xiàn)中册舞,用一個(gè)集合存放了加載類的引用蕴掏。另一方面,一個(gè)Class對(duì)象總是會(huì)引用它的類加載器调鲸。調(diào)用getClassLoader()方法囚似,即可獲取對(duì)應(yīng)的類加載器。所以class實(shí)例和loader1之間是一個(gè)雙向的關(guān)聯(lián)關(guān)系线得。Class<->ClassLoader
一個(gè)類的實(shí)例總是引用代表這個(gè)類的Class對(duì)象饶唤。在Object中定義了getClass()方法獲取對(duì)于Class實(shí)例的引用。Java類中也存在靜態(tài)class屬性贯钩。引用代表當(dāng)前這個(gè)類的Class對(duì)象募狂。所以類的實(shí)例和Class對(duì)象是一個(gè)單向的引用關(guān)系。 Class <- instance.
7-2-2:畫圖分析
7-2-3: 結(jié)論
打印的兩次的哈希值不同角雷。因此clz變量?jī)纱我昧瞬煌腃lass對(duì)象祸穷。可見Java虛擬機(jī)的生命周期中勺三,對(duì)A類先后加載兩次雷滚。
7-3:卸載
當(dāng)A類被加載、鏈接吗坚、和初始化后祈远,它的聲明周期就開始了。代表A的Class對(duì)象不再引用商源,及不可被觸及车份,Class對(duì)象就會(huì)結(jié)束生命周期。A類的方法區(qū)內(nèi)的數(shù)據(jù)也會(huì)被卸載牡彻。從而結(jié)束A類的生命周期扫沼。所以一個(gè)類何時(shí)結(jié)束生命周期,取決于代表它的Class對(duì)象何時(shí)結(jié)束生命周期。
java虛擬機(jī)自帶的類加載所加載的類缎除,在虛擬機(jī)生命周期中严就,始終不會(huì)被卸載。因?yàn)閖ava虛擬機(jī)本身會(huì)一直引用這些類加載器器罐,而這些類加載器則會(huì)始終引用他們所加載的類的Class對(duì)象梢为,因此這些Class對(duì)象始終是可觸及的。
而用戶自定義的類加載所加載的類時(shí)可以被卸載的技矮。