摘要:當(dāng)程序使用某個(gè)類時(shí),如果該類還沒被初始化,加載到內(nèi)存中,則系統(tǒng)會通過加載連接初始化三個(gè)過程來對該類進(jìn)行初始化。一旦一個(gè)類被加載到中之后,就不會再次載入了。它既可以從本地文件系統(tǒng)獲取二進(jìn)制文件來加載類,也可以遠(yuǎn)程主機(jī)獲取二進(jìn)制文件來加載類。
當(dāng)程序使用某個(gè)類時(shí),如果該類還沒被初始化,加載到內(nèi)存中,則系統(tǒng)會通過加載、連接、初始化三個(gè)過程來對該類進(jìn)行初始化。該過程就被稱為類的初始化
類加載指將類的class文件讀入內(nèi)存,并為之創(chuàng)建一個(gè)java.lang.Class的對象
從本地文件系統(tǒng)加載的class文件
從JAR包加載class文件
從網(wǎng)絡(luò)加載class文件
把一個(gè)Java源文件動(dòng)態(tài)編譯,并執(zhí)行加載
類加載器類加載器通常無須等到“首次使用”該類時(shí)才加載該類,JVM允許系統(tǒng)預(yù)先加載某些類
類加載器就是負(fù)責(zé)加載所有的類,將其載入內(nèi)存中,生成一個(gè)java.lang.Class實(shí)例。一旦一個(gè)類被加載到JVM中之后,就不會再次載入了。
根類加載器(Bootstrap ClassLoader):其負(fù)責(zé)加載Java的核心類,比如String、System這些類
拓展類加載器(Extension ClassLoader):其負(fù)責(zé)加載JRE的拓展類庫
系統(tǒng)類加載器(System ClassLoader):其負(fù)責(zé)加載CLASSPATH環(huán)境變量所指定的JAR包和類路徑
用戶類加載器:用戶自定義的加載器,以類加載器為父類
類加載器之間的父子關(guān)系并不是繼承關(guān)系,是類加載器實(shí)例之間的關(guān)系
public static void main(String[] args) throws IOException { ClassLoader systemLoader = ClassLoader.getSystemClassLoader(); System.out.println("系統(tǒng)類加載"); Enumerationem1 = systemLoader.getResources(""); while (em1.hasMoreElements()) { System.out.println(em1.nextElement()); } ClassLoader extensionLader = systemLoader.getParent(); System.out.println("拓展類加載器" + extensionLader); System.out.println("拓展類加載器的父" + extensionLader.getParent()); }
結(jié)果
系統(tǒng)類加載 file:/E:/gaode/em/bin/ 拓展類加載器sun.misc.Launcher$ExtClassLoader@6d06d69c 拓展類加載器的父null
為什么根類加載器為NULL?
JVM類加載機(jī)制根類加載器并不是Java實(shí)現(xiàn)的,而且由于程序通常須訪問根加載器,因此訪問擴(kuò)展類加載器的父類加載器時(shí)返回NULL
全盤負(fù)責(zé),當(dāng)一個(gè)類加載器負(fù)責(zé)加載某個(gè)Class時(shí),該Class所依賴的和引用的其他Class也將由該類加載器負(fù)責(zé)載入,除非顯示使用另外一個(gè)類加載器來載入
父類委托,先讓父類加載器試圖加載該類,只有在父類加載器無法加載該類時(shí)才嘗試從自己的類路徑中加載該類
緩存機(jī)制,緩存機(jī)制將會保證所有加載過的Class都會被緩存,當(dāng)程序中需要使用某個(gè)Class時(shí),類加載器先從緩存區(qū)尋找該Class,只有緩存區(qū)不存在,系統(tǒng)才會讀取該類對應(yīng)的二進(jìn)制數(shù)據(jù),并將其轉(zhuǎn)換成Class對象,存入緩存區(qū)。這就是為什么修改了Class后,必須重啟JVM,程序的修改才會生效
URLClassLoader類URLClassLoader為ClassLoader的一個(gè)實(shí)現(xiàn)類,該類也是系統(tǒng)類加載器和拓展類加載器的父類(繼承關(guān)系)。它既可以從本地文件系統(tǒng)獲取二進(jìn)制文件來加載類,也可以遠(yuǎn)程主機(jī)獲取二進(jìn)制文件來加載類。
兩個(gè)構(gòu)造器
URLClassLoader(URL[] urls):使用默認(rèn)的父類加載器創(chuàng)建一個(gè)ClassLoader對象,該對象將從urls所指定的路徑來查詢并加載類
URLClassLoader(URL[] urls,ClassLoader parent):使用指定的父類加載器創(chuàng)建一個(gè)ClassLoader對象,其他功能與前一個(gè)構(gòu)造器相同
import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; import com.mysql.jdbc.Driver; public class GetMysql { private static Connection conn; public static Connection getConn(String url,String user,String pass) throws MalformedURLException, InstantiationException, IllegalAccessException, ClassNotFoundException, SQLException{ if(conn==null){ URL[]urls={new URL("file:mysql-connector-java-5.1.18.jar")}; URLClassLoader myClassLoader=new URLClassLoader(urls); Driver driver=(Driver) myClassLoader.loadClass("com.mysql.jdbc.Driver").newInstance(); Properties pros=new Properties(); pros.setProperty("user", user); pros.setProperty("password", pass); conn=driver.connect(url, pros); } return conn; } public static method1 getConn() throws MalformedURLException, InstantiationException, IllegalAccessException, ClassNotFoundException, SQLException{ URL[]urls={new URL("file:com.em")}; URLClassLoader myClassLoader=new URLClassLoader(urls); method1 driver=(method1) myClassLoader.loadClass("com.em.method1").newInstance(); return driver; } public static void main(String[] args) throws MalformedURLException, InstantiationException, IllegalAccessException, ClassNotFoundException, SQLException { System.out.println(getConn("jdbc:mysql://10.10.16.11:3306/auto?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true", "jiji", "jiji")); System.out.println(getConn()); } }
獲得URLClassLoader對象后,調(diào)用loanClass()方法來加載指定的類
自定義類加載器import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.Method; public class CompileClassLoader extends ClassLoader { // 讀取一個(gè)文件的內(nèi)容 @SuppressWarnings("resource") private byte[] getBytes(String filename) throws IOException { File file = new File(filename); long len = file.length(); byte[] raw = new byte[(int) len]; FileInputStream fin = new FileInputStream(file); // 一次讀取class文件的全部二進(jìn)制數(shù)據(jù) int r = fin.read(raw); if (r != len) throw new IOException("無法讀取全部文件" + r + "!=" + len); fin.close(); return raw; } // 定義編譯指定java文件的方法 private boolean compile(String javaFile) throws IOException { System.out.println("CompileClassLoader:正在編譯" + javaFile + "…….."); // 調(diào)用系統(tǒng)的javac命令 Process p = Runtime.getRuntime().exec("javac" + javaFile); try { // 其它線程都等待這個(gè)線程完成 p.waitFor(); } catch (InterruptedException ie) { System.out.println(ie); } // 獲取javac 的線程的退出值 int ret = p.exitValue(); // 返回編譯是否成功 return ret == 0; } // 重寫Classloader的findCLass方法 protected Class> findClass(String name) throws ClassNotFoundException { Class clazz = null; // 將包路徑中的.替換成斜線/ String fileStub = name.replace(".", "/"); String javaFilename = fileStub + ".java"; String classFilename = fileStub + ".class"; File javaFile = new File(javaFilename); File classFile = new File(classFilename); // 當(dāng)指定Java源文件存在,且class文件不存在,或者Java源文件的修改時(shí)間比class文件//修改時(shí)間晚時(shí),重新編譯 if (javaFile.exists() && (!classFile.exists()) || javaFile.lastModified() > classFile.lastModified()) { try { // 如果編譯失敗,或該Class文件不存在 if (!compile(javaFilename) || !classFile.exists()) { throw new ClassNotFoundException("ClassNotFoundException:" + javaFilename); } } catch (IOException ex) { ex.printStackTrace(); } } // 如果class文件存在,系統(tǒng)負(fù)責(zé)將該文件轉(zhuǎn)化成class對象 if (classFile.exists()) { try { // 將class文件的二進(jìn)制數(shù)據(jù)讀入數(shù)組 byte[] raw = getBytes(classFilename); // 調(diào)用Classloader的defineClass方法將二進(jìn)制數(shù)據(jù)轉(zhuǎn)換成class對象 clazz = defineClass(name, raw, 0, raw.length); } catch (IOException ie) { ie.printStackTrace(); } } // 如果claszz為null,表明加載失敗,則拋出異常 if (clazz == null) { throw new ClassNotFoundException(name); } return clazz; } // 定義一個(gè)主方法 public static void main(String[] args) throws Exception { // 如果運(yùn)行該程序時(shí)沒有參數(shù),即沒有目標(biāo)類 if (args.length < 1) { System.out.println("缺少運(yùn)行的目標(biāo)類,請按如下格式運(yùn)行java源文件:"); System.out.println("java CompileClassLoader ClassName"); } // 第一個(gè)參數(shù)是需要運(yùn)行的類 String progClass = args[0]; // 剩下的參數(shù)將作為運(yùn)行目標(biāo)類時(shí)的參數(shù),所以將這些參數(shù)復(fù)制到一個(gè)新數(shù)組中 String progargs[] = new String[args.length - 1]; System.arraycopy(args, 1, progargs, 0, progargs.length); CompileClassLoader cl = new CompileClassLoader(); // 加載需要運(yùn)行的類 Class> clazz = cl.loadClass(progClass); // 獲取需要運(yùn)行的類的主方法 Method main = clazz.getMethod("main", (new String[0]).getClass()); Object argsArray[] = { progargs }; main.invoke(null, argsArray); } }
JVM中除了根類加載器之外的所有類的加載器都是ClassLoader子類的實(shí)例,通過重寫ClassLoader中的方法,實(shí)現(xiàn)自定義的類加載器
loadClass(String name,boolean resolve):為ClassLoader的入口點(diǎn),根據(jù)指定名稱來加載類,系統(tǒng)就是調(diào)用ClassLoader的該方法來獲取制定累對應(yīng)的Class對象
findClass(String name):根據(jù)指定名稱來查找類
類的鏈接推薦使用findClass方法
當(dāng)類被加載后,系統(tǒng)會為之生成一個(gè)Class對象,接著將會進(jìn)入連接階段,鏈接階段負(fù)責(zé)把類的二進(jìn)制數(shù)據(jù)合并到JRE中
三個(gè)階段
驗(yàn)證:檢驗(yàn)被加載的類是否有正確的內(nèi)部結(jié)構(gòu),并和其他類協(xié)調(diào)一致
準(zhǔn)備:負(fù)責(zé)為類的類變量分配內(nèi)存。并設(shè)置默認(rèn)初始值
解析:將類的二進(jìn)制數(shù)據(jù)中的符號引用替換成直接引用
類的初始化JVM負(fù)責(zé)對類進(jìn)行初始化,主要對類變量進(jìn)行初始化
在Java中對類變量進(jìn)行初始值設(shè)定有兩種方式:①聲明類變量是指定初始值②使用靜態(tài)代碼塊為類變量指定初始值
JVM初始化步驟
假如這個(gè)類還沒有被加載和連接,則程序先加載并連接該類
假如該類的直接父類還沒有被初始化,則先初始化其直接父類
假如類中有初始化語句,則系統(tǒng)依次執(zhí)行這些初始化語句
類初始化時(shí)機(jī)創(chuàng)建類實(shí)例。也就是new的方式
調(diào)用某個(gè)類的類方法
訪問某個(gè)類或接口的類變量,或?yàn)樵擃愖兞抠x值
使用反射方式強(qiáng)制創(chuàng)建某個(gè)類或接口對應(yīng)的java.lang.Class對象
初始化某個(gè)類的子類,則其父類也會被初始化
直接使用java.exe命令來運(yùn)行某個(gè)主類
類加載機(jī)制(類加載過程和類加載器)
更多內(nèi)容可以關(guān)注微信公眾號,或者訪問AppZone網(wǎng)站
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/66010.html
摘要:如果需要支持類的動(dòng)態(tài)加載或需要對編譯后的字節(jié)碼文件進(jìn)行解密操作等,就需要與類加載器打交道了。雙親委派模型,雙親委派模型,約定類加載器的加載機(jī)制。任何之類的字節(jié)碼都無法調(diào)用方法,因?yàn)樵摲椒ㄖ荒茉陬惣虞d的過程中由調(diào)用。 jvm系列 垃圾回收基礎(chǔ) JVM的編譯策略 GC的三大基礎(chǔ)算法 GC的三大高級算法 GC策略的評價(jià)指標(biāo) JVM信息查看 GC通用日志解讀 jvm的card table數(shù)據(jù)...
摘要:實(shí)現(xiàn)這個(gè)口號的就是可以運(yùn)行在不同平臺上的虛擬機(jī)和與平臺無關(guān)的字節(jié)碼。類加載過程加載加載是類加載的第一個(gè)階段,虛擬機(jī)要完成以下三個(gè)過程通過類的全限定名獲取定義此類的二進(jìn)制字節(jié)流。驗(yàn)證目的是確保文件字節(jié)流信息符合虛擬機(jī)的要求。 引言 我們知道java代碼編譯后生成的是字節(jié)碼,那虛擬機(jī)是如何加載這些class字節(jié)碼文件的呢?加載之后又是如何進(jìn)行方法調(diào)用的呢? 一 類文件結(jié)構(gòu) 無關(guān)性基石 ja...
摘要:類加載器類加載器執(zhí)行的操作就是上述加載階段做的事,通過一個(gè)類的全限定名來獲取定義這個(gè)類的二進(jìn)制字節(jié)流,類加載器可以分為下列三種。應(yīng)用程序類加載器,也稱為系統(tǒng)類加載器。 類加載流程: showImg(https://segmentfault.com/img/bV8SRP?w=1152&h=388);從上面這幅圖可以看出一個(gè)類從加載到卸載有7個(gè)階段,其中驗(yàn)證、準(zhǔn)備和解析這三個(gè)步驟統(tǒng)稱為連接...
摘要:加載階段在類的加載階段,虛擬機(jī)需要完成以下件事情通過一個(gè)類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流。驗(yàn)證階段驗(yàn)證是連接階段的第一步,這一階段的目的是為了確保文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會危害虛擬機(jī)自身的安全。 注:本篇文章中的內(nèi)容是根據(jù)《深入理解Java虛擬機(jī)--JVM高級特性與最佳實(shí)踐》而總結(jié)的,如有理解錯(cuò)誤,歡迎大家指正! 虛擬機(jī)把描述類的數(shù)據(jù)從Class文件...
摘要:在加載階段,虛擬機(jī)要完成件事情通過一個(gè)類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流。前面的階段中,除了加載的時(shí)候,可以由用戶指定自定義類加載器之外,別的都是由虛擬機(jī)主導(dǎo)控制。 java類加載機(jī)制 代碼編譯的結(jié)果從本地機(jī)器碼轉(zhuǎn)變?yōu)樽止?jié)碼,是存儲格式發(fā)展的一小步,確實(shí)編程語言發(fā)展的一大步 虛擬機(jī)把描述類的數(shù)據(jù)從class文件加載到內(nèi)存,并對數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直...
閱讀 2164·2023-04-26 00:43
閱讀 2685·2021-11-22 15:22
閱讀 3816·2021-11-11 16:55
閱讀 969·2021-11-04 16:06
閱讀 1787·2019-08-30 14:12
閱讀 999·2019-08-30 14:02
閱讀 3368·2019-08-29 17:05
閱讀 1417·2019-08-29 12:27