當(dāng)程序使用某個(gè)類時(shí)兽埃,如果該類還沒被初始化板丽,加載到內(nèi)存中,則系統(tǒng)會(huì)通過加載刹勃、連接堪侯、初始化三個(gè)過程來對(duì)該類進(jìn)行初始化。該過程就被稱為類的初始化
類加載
指將類的class文件讀入內(nèi)存荔仁,并為之創(chuàng)建一個(gè)java.lang.Class的對(duì)象
類文件來源
- 從本地文件系統(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中之后掌呜,就不會(huì)再次載入了。
- 根類加載器(Bootstrap ClassLoader):其負(fù)責(zé)加載Java的核心類坪哄,比如String质蕉、System這些類
- 拓展類加載器(Extension ClassLoader):其負(fù)責(zé)加載JRE的拓展類庫(kù)
- 系統(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)類加載");
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());
}
結(jié)果
系統(tǒng)類加載
file:/E:/gaode/em/bin/
拓展類加載器sun.misc.Launcher$ExtClassLoader@6d06d69c
拓展類加載器的父null
為什么根類加載器為NULL?
根類加載器并不是Java實(shí)現(xiàn)的模暗,而且由于程序通常須訪問根加載器禁悠,因此訪問擴(kuò)展類加載器的父類加載器時(shí)返回NULL
JVM類加載機(jī)制
- 全盤負(fù)責(zé),當(dāng)一個(gè)類加載器負(fù)責(zé)加載某個(gè)Class時(shí)兑宇,該Class所依賴的和引用的其他Class也將由該類加載器負(fù)責(zé)載入碍侦,除非顯示使用另外一個(gè)類加載器來載入
- 父類委托,先讓父類加載器試圖加載該類隶糕,只有在父類加載器無法加載該類時(shí)才嘗試從自己的類路徑中加載該類
- 緩存機(jī)制瓷产,緩存機(jī)制將會(huì)保證所有加載過的Class都會(huì)被緩存,當(dāng)程序中需要使用某個(gè)Class時(shí)枚驻,類加載器先從緩存區(qū)尋找該Class濒旦,只有緩存區(qū)不存在,系統(tǒng)才會(huì)讀取該類對(duì)應(yīng)的二進(jìn)制數(shù)據(jù)再登,并將其轉(zhuǎn)換成Class對(duì)象尔邓,存入緩存區(qū)。這就是為什么修改了Class后锉矢,必須重啟JVM梯嗽,程序的修改才會(huì)生效
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對(duì)象缠俺,該對(duì)象將從urls所指定的路徑來查詢并加載類
URLClassLoader(URL[] urls,ClassLoader parent):使用指定的父類加載器創(chuàng)建一個(gè)ClassLoader對(duì)象显晶,其他功能與前一個(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對(duì)象后,調(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對(duì)象
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對(duì)象
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)類崩掘,請(qǐng)按如下格式運(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的該方法來獲取制定累對(duì)應(yīng)的Class對(duì)象
findClass(String name):根據(jù)指定名稱來查找類
推薦使用findClass方法
類的鏈接
當(dāng)類被加載后挽放,系統(tǒng)會(huì)為之生成一個(gè)Class對(duì)象绍赛,接著將會(huì)進(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ù)中的符號(hào)引用替換成直接引用
類的初始化
JVM負(fù)責(zé)對(duì)類進(jìn)行初始化,主要對(duì)類變量進(jìn)行初始化
在Java中對(duì)類變量進(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è)類或接口對(duì)應(yīng)的java.lang.Class對(duì)象
- 初始化某個(gè)類的子類,則其父類也會(huì)被初始化
- 直接使用java.exe命令來運(yùn)行某個(gè)主類