當程序使用某個類時,若是該類還沒被初始化,加載到內存中,則系統會經過加載、鏈接、初始化三個過程來對該類進行初始化。該過程就被稱爲類的初始化java
指將類的class文件讀入內存,併爲之建立一個java.lang.Class的對象mysql
從本地文件系統加載的class文件git
從JAR包加載class文件github
從網絡加載class文件sql
把一個Java源文件動態編譯,並執行加載數組
類加載器一般無須等到「首次使用」該類時才加載該類,JVM容許系統預先加載某些類緩存
類加載器就是負責加載全部的類,將其載入內存中,生成一個java.lang.Class實例。一旦一個類被加載到JVM中以後,就不會再次載入了。微信
根類加載器(Bootstrap ClassLoader):其負責加載Java的核心類,好比String、System這些類網絡
拓展類加載器(Extension ClassLoader):其負責加載JRE的拓展類庫網站
系統類加載器(System ClassLoader):其負責加載CLASSPATH環境變量所指定的JAR包和類路徑
用戶類加載器:用戶自定義的加載器,以類加載器爲父類
類加載器之間的父子關係並非繼承關係,是類加載器實例之間的關係
public static void main(String[] args) throws IOException { ClassLoader systemLoader = ClassLoader.getSystemClassLoader(); System.out.println("系統類加載"); Enumeration<URL> em1 = systemLoader.getResources(""); while (em1.hasMoreElements()) { System.out.println(em1.nextElement()); } ClassLoader extensionLader = systemLoader.getParent(); System.out.println("拓展類加載器" + extensionLader); System.out.println("拓展類加載器的父" + extensionLader.getParent()); }
結果
系統類加載 file:/E:/gaode/em/bin/ 拓展類加載器sun.misc.Launcher$ExtClassLoader@6d06d69c 拓展類加載器的父null
爲何根類加載器爲NULL?
根類加載器並非Java實現的,並且因爲程序一般須訪問根加載器,所以訪問擴展類加載器的父類加載器時返回NULL
全盤負責,當一個類加載器負責加載某個Class時,該Class所依賴的和引用的其餘Class也將由該類加載器負責載入,除非顯示使用另一個類加載器來載入
父類委託,先讓父類加載器試圖加載該類,只有在父類加載器沒法加載該類時才嘗試從本身的類路徑中加載該類
緩存機制,緩存機制將會保證全部加載過的Class都會被緩存,當程序中須要使用某個Class時,類加載器先從緩存區尋找該Class,只有緩存區不存在,系統纔會讀取該類對應的二進制數據,並將其轉換成Class對象,存入緩存區。這就是爲何修改了Class後,必須重啓JVM,程序的修改纔會生效
URLClassLoader爲ClassLoader的一個實現類,該類也是系統類加載器和拓展類加載器的父類(繼承關係)。它既能夠從本地文件系統獲取二進制文件來加載類,也能夠遠程主機獲取二進制文件來加載類。
兩個構造器
URLClassLoader(URL[] urls):使用默認的父類加載器建立一個ClassLoader對象,該對象將從urls所指定的路徑來查詢並加載類
URLClassLoader(URL[] urls,ClassLoader parent):使用指定的父類加載器建立一個ClassLoader對象,其餘功能與前一個構造器相同
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對象後,調用loanClass()方法來加載指定的類
import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.Method; public class CompileClassLoader extends ClassLoader { // 讀取一個文件的內容 @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文件的所有二進制數據 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 + "…….."); // 調用系統的javac命令 Process p = Runtime.getRuntime().exec("javac" + javaFile); try { // 其它線程都等待這個線程完成 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); // 當指定Java源文件存在,且class文件不存在,或者Java源文件的修改時間比class文件//修改時間晚時,從新編譯 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文件存在,系統負責將該文件轉化成class對象 if (classFile.exists()) { try { // 將class文件的二進制數據讀入數組 byte[] raw = getBytes(classFilename); // 調用Classloader的defineClass方法將二進制數據轉換成class對象 clazz = defineClass(name, raw, 0, raw.length); } catch (IOException ie) { ie.printStackTrace(); } } // 若是claszz爲null,代表加載失敗,則拋出異常 if (clazz == null) { throw new ClassNotFoundException(name); } return clazz; } // 定義一個主方法 public static void main(String[] args) throws Exception { // 若是運行該程序時沒有參數,即沒有目標類 if (args.length < 1) { System.out.println("缺乏運行的目標類,請按以下格式運行java源文件:"); System.out.println("java CompileClassLoader ClassName"); } // 第一個參數是須要運行的類 String progClass = args[0]; // 剩下的參數將做爲運行目標類時的參數,因此將這些參數複製到一個新數組中 String progargs[] = new String[args.length - 1]; System.arraycopy(args, 1, progargs, 0, progargs.length); CompileClassLoader cl = new CompileClassLoader(); // 加載須要運行的類 Class<?> clazz = cl.loadClass(progClass); // 獲取須要運行的類的主方法 Method main = clazz.getMethod("main", (new String[0]).getClass()); Object argsArray[] = { progargs }; main.invoke(null, argsArray); } }
JVM中除了根類加載器以外的全部類的加載器都是ClassLoader子類的實例,經過重寫ClassLoader中的方法,實現自定義的類加載器
loadClass(String name,boolean resolve):爲ClassLoader的入口點,根據指定名稱來加載類,系統就是調用ClassLoader的該方法來獲取制定累對應的Class對象
findClass(String name):根據指定名稱來查找類
推薦使用findClass方法
當類被加載後,系統會爲之生成一個Class對象,接着將會進入鏈接階段,連接階段負責把類的二進制數據合併到JRE中
三個階段
驗證:檢驗被加載的類是否有正確的內部結構,並和其餘類協調一致
準備:負責爲類的類變量分配內存。並設置默認初始值
解析:將類的二進制數據中的符號引用替換成直接引用
JVM負責對類進行初始化,主要對類變量進行初始化
在Java中對類變量進行初始值設定有兩種方式:①聲明類變量是指定初始值②使用靜態代碼塊爲類變量指定初始值
JVM初始化步驟
假如這個類尚未被加載和鏈接,則程序先加載並鏈接該類
假如該類的直接父類尚未被初始化,則先初始化其直接父類
假如類中有初始化語句,則系統依次執行這些初始化語句
建立類實例。也就是new的方式
調用某個類的類方法
訪問某個類或接口的類變量,或爲該類變量賦值
使用反射方式強制建立某個類或接口對應的java.lang.Class對象
初始化某個類的子類,則其父類也會被初始化
直接使用java.exe命令來運行某個主類
更多內容能夠關注微信公衆號,或者訪問AppZone網站