javaclassloader
⑴ 五、ClassLoader雙親委派載入模式
獲取啟動類載入器載入的目錄 System.getProperty("sun.boot.class.path");
獲取擴展類載入器載入的目錄 System.getProperty("java.ext.dirs");
獲取應用類載入器載入的目錄 System.getProperty("java.class.path");
將test.Test2.class移到啟動類載入器載入的目錄,然後其載入類
啟動類載入器載入目錄如下
通過上述案列可以分析出,test.Test2是由啟動類載入器載入的,驗證了雙親委派優先父載入器載入
通過上述案列得出AESKeyGenerator是由擴展類載入器載入的,手動修改擴展類載入器載入的目錄,修改完成後在修改後的目錄是找不到AESKeyGenerator,所以會報錯
java -Djava.ext.dirs=./ test.Test19
將本地目錄作為擴展類載入器載入的目錄
同一個命名空間內的類時相互可見的
子載入器的命名空間包含所有的父載入器的命名空間。因此由子載入器載入的類可以看見父載入器載入的類。例如系統類載入器載入的類能看見跟類載入器載入的類。
由父載入器載入的類不能看見子載入器載入的類。
如果兩個載入器之間沒有直接或者間接的父子關系,那麼它們各自載入的類相互不可見
上述案列是因為classLoader1與classLoader2的父載入器都為AppClassLoader,所以classLoader1載入完Person後在內存的緩存中是有Person.class的,classLoader2委託AppClassLoader載入的時候直接就從內存中獲取Person.class,所以他們是一致的,method方法也是可以執行的
當刪除當前classpath下的Person.class文件,將Person.class文件移到D盤下
此時當前的classPath下沒有Person.class文件,會分別由classLoader1載入器與classLoader2載入器載入,會存在classLoader1載入器的命名空間以及classLoader2載入器的命名空間,根據 如果兩個載入器之間沒有直接或者間接的父子關系,那麼它們各自載入的類相互不可見 的規則那麼classLoader1與classLoader2載入的類時相互不可見的也是不相同的,這里會有很有趣的問題 java.lang.ClassCastException: test.Person cannot be cast to test.Person
修改擴展類載入路勁為當前路徑
執行 java -Djava.ext.dirs=./ test.Test22
可以發現還是使用AppClassLoader載入的Test22以及Test1
擴展類載入器不能直接載入.class文件,需要將.class文件打成jar包
jar cvf test.jar test/Test1.class
再次執行 ,可以發現Test1是通過擴展類載入器載入的,因為此處只將Test1打成jar包了
1. 可以確保java核心庫的類型安全: 例如所有的JAVA應用都至少會引用java.lang.Object類,也就是說在jvm的運行期間,java.lang.Object類會被載入到java虛擬機中,如果這個過程是由自己定義的java類載入器完成的,那麼很有可能在jvm中存在多個版本的java.lang.Object類,也就是說在jvm的運行期間而這些類之間是相互不兼容的(命名空間不同導致的)。
藉助雙親委派機制,java核心類庫的載入必須由啟動類載入器載入,從而確保java中使用的核心類庫的版本統一,他們之間是相互兼容的
2. 可以保證java核心類庫載入的類不會被自定義類所替代。
3. 不同類的載入器可以為相同名稱的類(binary name)創建額外的命名空間。相同名稱的類可以並存與不同命名空間的內存中,不同類載入器載入的類之間是相互不兼容的就相當於在java虛擬機內部創建了一個又一個相互獨立並且隔離的的java空間,這類技術在很多框架都得到了使用。
在運行期間,一個Java類是由該類的完全限定名(binary name,二進制名)和用於載入該類的定義類載入器(defining loader)所共同決定的。如果同樣名字(即相同的限定名)的類是由兩個不同的載入類所載入,那麼這些類就是不同的,即便.class文件的位元組碼完全一樣,並且從相同的位置載入也是如此。
在Oracle的Hotspot實現中,系統屬性 sun.boot.class.path如果被修改錯了,則運行會出錯,提示如下錯誤信息:
Error occureed ring initialization of VM
java/lang/NoClassDefFoundError: java/lang/Object
內建於JVM中的啟動類載入器會載入java.lang.ClassLoader以及其他的java平台類,當JVM啟動時,一塊特殊的機器碼會運行,它會載入擴展類載入器以及系統類載入器,這塊特殊的機器碼叫做啟動類載入器(BootStrap)。啟動類載入器並不是java類,而其他的載入器都是java類,啟動類載入器是特定於平台的機器指令,它負責開啟整個載入過程
所有類載入器(除了啟動類載入器)都被實現為Java類。不過總歸要有一個組件來載入第一個java類載入器,從而讓整個載入過程能夠順利進行,載入第一個純JAVA類載入器就是啟動類載入器的職責。
啟動類載入器還會負責載入JRE正常運行時所需的基本組件,這包括java.util與java.lang包中的類等等
java -Djava.system.class.loader=test.Test16 test.Test23
⑵ java classloader怎麼使用
Java是一種介於解釋與編繹之間的語言,
Java代碼首先編繹成位元組碼,
在運行的時候再翻譯成機器碼。
這樣在運行的時候我們就可以通過Java提供的反射方法(reflect)來得到一個Object的Class的額外信息,
靈活性很大,可以簡化很多操作。
Class:
任何一個Object都能通過getClass()這個方法得到它在運行期間的Class。
得到這個Class之後可做的事情就多了,
比如動態得到它的構造函數,
成員變數,
方法等等。
還可以再生成一份新的實例,
下面只給出幾個我們常用的方法,
更詳細的用法參照Java
API
Ø
Class
Class.forName(String
className)
throws
ClassNotFoundException:
這是個靜態方法,
通過一個Class的全稱來得到這個Class。
Ø
String
getName()
取得這個Class的全稱,
包括package名。
Ø
Object
newInstance()
得到一個實例,
調用預設的構造函數。
例如我們有一個類:
com.some.util.MyClass
如果得到它的一個實例呢?
可能有以下兩種方法:
MyClass
myClass
=
new
MyClass(),
直接通過操作符new生成;
或者:
MyClass
myClass
=
(MyClass)
Class.forName(「com.some.util.MyClass」).newInstance();
也許有人就會懷疑第二種方法實際意義,
能夠直接new出來幹嘛繞彎。
但實際上它的用處卻很大,
舉個例子:
用過struts的人都知道,
在action-config.xml當中定義了一系列的formBean與actionBean,
當然每個form與action都具有同類型,
這樣在一個request過來的時候我可以動態的生成一個form與action的實例進行具體的操作,
但在編碼的時候我並不知道具體是何種的form與action,
我只調用它們父類的方法。
你如果要用第一種方法的話,
你得在編碼的時候通過一個標志來判斷每一次request需要具體生成的form與action,
代碼的靈活性大大降低。
總的來說在面向介面的編程當中經常使用這種方法,
比如不同資料庫廠家的JDBC
Driver都是從標準的JDBC介面繼承下去的,
我們在寫程序的時候用不著管最終是何種的Driver,
只有在運行的時候確定。
還有XML的Parser也是,
我們使用的只是標準的介面,
最後到底是誰來實現它的,
我們用不著去管。
ClassLoader:
ClassLoader是一個抽象類,一般的系統有一個預設的ClassLoader用來裝載Class,
用ClassLoader.getSystemClassLoader()可以得到。不過有時候為了安全或有其它的特殊需要我們可以自定義自己的ClassLoader來進行loader一些我們需要的Class,
比如有的產品它用了自己的ClassLoader可以指定Class只從它指定的特定的JAR文件裡面來loader,如果你想通過覆蓋ClassPath方法來想讓它用你的Class是行不通的。
有興趣的可以參照Java
API
的更詳細的用法說
⑶ java 每個類都有一個 classloader
不是這樣的,通常只會有一個.
Java虛擬機啟動時會調用系統類載入器BootstrapClassLoader去載入ExtClassLoader和ApplicationClassLoader.
ExtClassLoader:自定義的類載入器.
ApplicationClassLoader:當你用的某個類時會通過它進行載入,當然你也可以通過代碼用自定義的類載入器進行class的載入.
載入是指將類載入到java虛擬機中.
看下上面的圖片會發現class是Data1類中的一個靜態域,這說明在Data1類的多個實例中只有一個class對象,一個對象怎麼會返回多個classloader呢。classloader實在class載入到虛擬機時賦上去的.
⑷ java classloader 怎麼載入jar包
設我們有一個hello.jar文件,裡面有一個Util類,我們希望在運行期調將這個jar包放入到我們運行環境並且調用裡面的Util.getVersion方法。怎麼實現?
在java中,我們的類都是通過ClassLoader來載入的,同時ClassLoader具有層級關系,當某個類找不到時,它會去他的父類載入器去尋找,如果依然找不到,就拋出ClassNotFoundException了。
為了動態載入hello.jar裡面的Util類,我們需要將這個jar包放入到我們的類載入器中去,然後再獲取裡面的類。如下面的代碼。
// 位於hello.jar
package com.flyingzl;
public class Util {
public static void getVersion(){
System.out.println("java version: " + System.getProperty("java.version"));
}
}
⑸ Java的ClassLoader對象如何關閉
URLClassLoader loader =new URLClassLoader(new URL[] { new URL("file:" + jarRoute) });
loader.close();
⑹ 怎麼 classloader 載入一個java源文件
1.類載入器深入剖析
Java虛擬機與程序的生命周期 :
當我們執行一個java程序的時候 , 會啟動一個JVM進程 , 當程序執行完之後 , JVM進程就消亡了 ;
在如下情況下JVM將結束聲明周期 :
System.exit(int)方法 , 當執行這個方法的時候 , 虛擬機會退出 ; 這個方法傳入一個整形參數 , 這個參數是狀態嗎 : 如果這個整形是 0 的話 , 就是正常退出 , 如果不是0的話 , 就是異常退出 ;
程序正常結束 ;
程序執行過程中 , 遇到了異常或錯誤 , 而異常終止 : 如果我們在程序中出現了異常 , 而不去處理 , 會將異常一直拋給main函數 , main函數會將異常拋給JVM , JVM如果處理不了異常 , JVM就會異常退出 ;
由於操作系統出現錯誤導致JVM進程終止 : JVM所依賴的平台出現錯誤 , 導致JVM終止 ;
2.類的載入,連接和初始化
載入 : 查找並載入類的二進制數據 , 將class位元組碼文件載入到內存中 ;
連接 :
-
驗證
: 確保被載入的類的正確性 , 使用javac 編譯工具生成的位元組碼文件能通過驗證 , 如果不是由javac編譯生成的位元組碼文件 , 如果自己生成的位元組碼文件不符合JVM虛擬機對位元組碼文件的要求的話 , 可能會出現驗證通不過的情況 ; 比如說隨便拿一個文件 , 將後綴名直接修改為.class , 這樣的位元組碼文件肯定不合法 ;
-
准備
: 為類的靜態變數分配內存 , 並將其初始化為默認值 ;
-
解析
: 把類中的符號引用轉為直接引用 ;
初始化 : 為類的靜態變數賦予正確的初始值(正確的值指的是用戶賦的值) ;
-好像這個與連接階段的准備有些重復 , 在連接的准備階段只是賦予初始變數 , 如果用戶給這個變數賦了初始值 , 那麼這個變數在連接的准備階段仍然會賦予初始值 ;
-在這個階段 , 才會真正的將初始值賦給靜態變數 ;
Java程序對類的使用方式有 主動使用 和 被動使用 ;
所有的JVM實現 , 必須在每個類或者介面 , 被java程序 「首次主動使用」 時才初始化他們 ;
主動使用 :
創建類的實例 ;
訪問某個類或介面的靜態變數 , 或者對該靜態變數賦值 ;
調用類的靜態方法 ;
反射 : Class.forName(「類名」) ;
初始化一個類的子類 , 看做對父類的主動使用 ;
java虛擬機啟動的時候 , 被標明啟動類的類 , 即包含main方法的類 , 程序的入口 ;
除了上面6種主動使用之外 , 其它的情況均為被動使用 , 其它情況都不會執行第三步初始化 ;
3.類的載入
(1)概念
類的載入 : 指的是將類的.class文件中的二進制數據讀入到內存中 , 將其放在運行時數據區的方法區內 , 然後再堆區創建一個java.lang.Class對象 , 用來封裝類在方法區內的數據結構 ;
反射 : 反射就是跟句堆區的位元組碼文件 , 獲取方法去的數據結構 ;
解析 : Class對象是由JVM自己創建的 , 所有的對象都是經過Class對象創建 , 這個Class對象是反射的入口, 通過Class對象 , 可以關聯到目標class位元組碼文件的內部結構 ;
所有的類對應的Class對象都是唯一的一個 , 這個類是由JVM進行創建的 , 並且只有JVM才會創建Class對象 ;
類載入的最終產品是位於堆區中的Class對象 , Class對象封裝了類在方法區內的數據結構 , 並且向Java程序員提供了訪問方法區內的數據結構的介面(反射用的介面) ;
(2)載入.class文件的方式
從本地系統中直接載入 : 編譯好的.class位元組碼文件直接從硬碟中載入 ;
通過網路下載.class文件 : 將class位元組碼文件放在網路空間中 , 使用URLClassLoader來載入在網路上的.class位元組碼文件 , 使用默認的父親委託機制載入位元組碼文件 ;
從zip , jar 等壓縮文件中載入位元組碼文件 : 在開發的時候 , 導入jar包 , 就是這種方式 ;
從專有的資料庫中提取位元組碼文件 ;
將java源文件動態編譯為位元組碼文件 ;
(3)類載入器
l Java虛擬機自帶的類載入器 :
-根類載入器 ( Bootstrap ) : 是C++寫的 , 程序員無法再java代碼中獲取這個類 , 如果使用getClassLoader()方法獲取到的是一個null值 ;
package jvm;
Java代碼
public class ClassLoaderTest {
public static void main(String[] args) throws Exception {
//java.lang包下的類使用的是跟類載入器進行載入的
Class clazz = Class.forName("java.lang.String");
System.out.println(clazz.getClassLoader());
//自定義的類使用的是應用類載入器(系統載入器)
Class clazz2 = Class.forName("jvm.C");
System.out.println(clazz2.getClassLoader());
}
}
class C{}
執行結果 :
null
Java代碼
sun.misc.Launcher$AppClassLoader@1372a1a
-擴展類載入器 ( Extension ) : Java編寫 ;
-系統類載入器(應用載入器) ( System ) : Java編寫 ;
用戶自定義的類載入器 :
-自定義的類載入器都是java.lang.ClassLoader子類 ;
-用戶可以定製類的載入方式
String類是由根類載入器進行載入的 , 我們可以調用Class對象的
關於代理中創建對象的類載入器 : 創建代理對象的時候 , 動態創建一個類 , 然後使用指定的類載入器將這個類載入到內存中 , 然後用載入到內存中的類生成代理對象 ;
創建代理對象的方法 : newProxyInstance(ClassLoader loader , Class [] Interfaces , InvocationHandler h )
loader 是定義的代理類的類載入器 , 中間的介面數組是代理類的要實現的介面列表 , h 是指派方法調用的調用處理程序 ;
類載入器並不需要在某個類被 「首次主動使用」 時再載入它 :
-預載入機制 : JVM規范允許類載入器在預料某個類將要被使用的時就預先載入它 ;
-報錯時機 : 如果在預載入的過程中遇到了位元組碼文件缺失或者存在錯誤的情況 , 類載入器會在程序首次主動使用(上面提到的六種情況)該類的時候報錯(LinkageError錯誤) ;
-不報錯時機 : 如果這個錯誤的位元組碼文件所對應的類一直沒有被使用 , 那麼類載入器就不會報告錯誤 ,即便有錯誤也不會報錯 ;
LinkageError : 這個錯誤是Error的子類 , 程序不能處理這些錯誤 , 這些錯誤都是由虛擬機來處理 , 這個錯誤表示出錯的是子類 , 在一定程序上依賴於另一個類 , 在編譯了前面一個類的時候 , 與後面所依賴的類出現了不兼容的情況 ;
例如 : 我們使用了jdk 1.6 在編譯一個程序 , 但是運行環境是jre1.5的 , 就會出現LinkageError錯誤 ;
4.類的連接
(1)定義
類被載入之後 , 就進入鏈接階段 ; 鏈接 : 將已讀入內存的二進制數據合並到虛擬機的運行時環境中去 ;
鏈接顧名思義就是講類與類之間進行關聯 , 例如我們在類A中調用了類B , 在鏈接過程中 , 就將A與B進行鏈接 ,將面向對象語言轉化為面向過程語言 ;
(2)類的驗證
類文件的結構檢查 : 確保類文件遵從java類文件的固定格式 , 開始類的描述 , 聲明 , 方法調用格式等 ;
語義檢查 : 確保類本身符合java語言的語法規定 , 比如final類型的類沒有子類 , final類型的方法沒有被覆蓋 ,在eclipse中這種錯誤編譯的時候不能通過 , 但是通過其他的方法可以生成錯誤的位元組碼文件 , 這里就是檢測惡意生成的位元組碼文件 ;
位元組碼驗證 : 確保位元組碼流可以被JVM安全的執行 , 位元組碼流代表java方法(包括靜態方法和實例方法) , 它是由被稱作操作碼的單位元組指令組成的序列 , 每一個操作碼後面跟著一個或多個操作數 , 位元組碼驗證步驟會檢查每個操作碼是否合法 , 即是否有著合法的操作數 ;
下面是指令碼組成的序列 , 類似於微指令 :
Jvm編譯指令代碼代碼
// Compiled from ByteToCharCp1122.java (version 1.5 : 49.0, super bit)
public class sun.io.ByteToCharCp1122 extends sun.io.ByteToCharSingleByte {
// Field descriptor #17 Lsun/nio/cs/ext/IBM1122;
private static final sun.nio.cs.ext.IBM1122 nioCoder;
// Method descriptor #18 ()Ljava/lang/String;
// Stack: 1, Locals: 1
public java.lang.String getCharacterEncoding();
0 ldc <String "Cp1122"> [1]
2 areturn
Line numbers:
[pc: 0, line: 25]
// Method descriptor #2 ()V
// Stack: 2, Locals: 1
public ByteToCharCp1122();
0 aload_0 [this]
1 invokespecial sun.io.ByteToCharSingleByte() [25]
4 aload_0 [this]
5 getstatic sun.io.ByteToCharCp1122.nioCoder : sun.nio.cs.ext.IBM1122 [23]
8 invokevirtual sun.nio.cs.ext.IBM1122.getDecoderSingleByteMappings() : java.lang.String [27]
11 putfield sun.io.ByteToCharSingleByte.byteToCharTable : java.lang.String [24]
14 return
Line numbers:
[pc: 0, line: 28]
[pc: 4, line: 29]
[pc: 14, line: 30]
// Method descriptor #2 ()V
// Stack: 2, Locals: 0
static {};
0 new sun.nio.cs.ext.IBM1122 [15]
3 p
4 invokespecial sun.nio.cs.ext.IBM1122() [26]
7 putstatic sun.io.ByteToCharCp1122.nioCoder : sun.nio.cs.ext.IBM1122 [23]
10 return
Line numbers:
[pc: 0, line: 22]
}
l 二進制兼容性的驗證 : 確保相互引用的類之間協調一致的 ; 例如在A類的a()方法中調用B類的b()方法 , JVM在驗證A類的時候 , 會驗證B類的b()方法 , 加入b()方法不存在 , 或者版本不兼容(A,B兩類使用不同的JDK版本編譯) , 會拋出NoSuchMethodError錯誤 ;
(3)准備階段
在准備階段 , JVM為類的靜態變數分配內存空間 , 並設置默認的初始值 . 例如下面的Sample類 , 在准備階段 ,為int類型的靜態變數分配4個位元組 , 並賦予初始值 0 ; 為long 類型的靜態變數 b , 分配8個位元組 , 並賦予初始值 0 ;
PS : 在java中基本類型變數佔用的空間是一定的 , java運行在JVM上的 , 在C中 , 就要根據平台變化而變化了 ;
public class Sample {
Java代碼
private static int a = 1 ;
private static long b ;
static {
b = 2 ;
}
(4)類的解析
在解析階段 , JVM 會把類的二進制數據中的符號引用替換為直接引用 , 例如在A類中的a()方法引用B類中的b()方法 ;
在A類的二進制數據中包含了一個對B類的b()方法的符號引用 , 這個符號引用由b()方法的全名和相關的描述符組成 , 在Java解析階段 , 就會把這個符號引用替換為指針 , 這個指針就是C語言中的指針了 , 該指針指向B類的b()方法在方法區中的內存位置 , 這個指針就是直接引用 ;
5.類的初始化
在初始化階段 , Java虛擬機執行類的初始化操作 , 為類的靜態變數賦予初始值 , 在程序中 , 靜態變數初始化有兩種途徑 :
直接在聲明處進行初始化 , 例如下面的Sample中的 變數a ;
在靜態代碼塊中進行初始化 , 例如下面的Sample中的變數b ;
Java代碼
public class Sample {
private static int a = 1 ;
private static long b ;
static {
b = 2 ;
}
}
6.面試題介紹
Java代碼
public class PrepareOrInit {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
System.out.println(singleton.count1);
System.out.println(singleton.count2);
}
}
class Singleton{
private static Singleton singleton = new Singleton() ;
public static int count1 ;
public static int count2 = 0 ;
private Singleton(){
count1 ++ ;
count2 ++ ;
}
public static Singleton getInstance(){
return singleton ;
}
}
執行結果 : 1 0
分析 : 這段代碼與類的鏈接中的准備階段 和 初始化階段 有關系 , 准備階段是給靜態的欄位賦予默認值 , 初始化階段給靜態變數賦予正確的值 , 即用戶的值 ;
在主函數中 , 調用了類的靜態方法 , 相當於主動使用 , 這里調用了類的靜態方法 ;
之後進行連接的准備操作 , 給類中的靜態變數賦予初值 , singleton值為null , count1 與 count2 值為0 ;
執行初始化操作 , 給類中的靜態變數賦予正確的值 , 給singleton變數賦予正確的值 , 調用構造方法 , 此時count1與 count2執行自增操作 , 兩者都變成1 , 然後執行count1的賦予正確值操作 , 這里用戶沒有賦值操作 , count2 用戶進行了 賦值為0的操作 , 0將原來的1覆蓋掉了 , 因此結果為 1 , 0 ;
Java代碼
public class PrepareOrInit {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
System.out.println(singleton.count1);
System.out.println(singleton.count2);
}
}
class Singleton{
public static int count1 ;
public static int count2 = 0 ;
private static Singleton singleton = new Singleton() ;
private Singleton(){
count1 ++ ;
count2 ++ ;
}
public static Singleton getInstance(){
return singleton ;
}
}
執行結果 : 1 1
在准備階段count1 和 count2 都賦值為0 , 然後在初始化階段 , 全部賦值為1 ;
⑺ java classloader 載入哪些類
BootStrap ClassLoader:稱為啟動類載入器,是Java類載入層次中最頂層的類載入器,負責載入JDK中的核心類庫,如:rt.jar、resources.jar、charsets.jar等
Extension ClassLoader:稱為擴展類載入器,負責載入Java的擴展類庫,默認載入JAVA_HOME/jre/lib/ext/目下的所有jar
App ClassLoader:稱為系統類載入器,負責載入應用程序classpath目錄下的所有jar和class文件
⑻ Java:如何編寫自己的Java類載入器
給你簡單介紹一下類載入器
1.類載入器就載入位元組碼文件(.class)
}
/*
相同的類載入器對同一個類進行載入,得到的hascode是相同的
* 不同的類載入器對同一類進行載入,得到的hascode是不一樣的*/public class Demo {public static void main(String[] args) {FileClassLoader loader = new FileClassLoader("c://myjava");FileClassLoader loader2=new FileClassLoader("c://myjava");try {Class<?> c = loader.findClass("com.lg.test.HelloWorld");Class<?> c0=loader.findClass("com.lg.test.HelloWorld");Class<?> c1=loader2.findClass("com.lg.test.HelloWorld");Class<?> c2=loader.findClass("com.lg.test.Demo01");Class<?> c3=loader.findClass("java.lang.String");System.out.println(c.hashCode());System.out.println(c.getClassLoader());System.out.println(c0.hashCode());System.out.println(c0.getClassLoader());System.out.println(c1.hashCode());System.out.println(c1.getClassLoader());System.out.println(c2.hashCode());System.out.println(c2.getClassLoader());System.out.println(c3.hashCode());System.out.println(c3.getClassLoader());}catch (ClassNotFoundException e) {e.printStackTrace();}}}