1. 類的加載機制深度解析
1.1 類的加載運行過程
如上圖所示,Java中的類的加載靠的是類加載器實現的,其中通過loadClass進行類加載的時候會經歷:加載》》驗證》》準備》》解析》》初始化
幾個步驟。
- 加載:查找磁盤中的類的字節碼文件並通過io操作讀入,只有類被真正使用的時候纔會被加載
- 驗證:驗證字節碼是否符合規範
- 準備:爲靜態變量分配內存空間,並且賦上默認的初始值。int類型賦值0、boolean類型賦值false、對象類型賦值null
- 解析:將符號引用(靜態方法)轉換爲直接引用
- 初始化:爲靜態變量賦上真正的值
注意:主類在運行時如果使用到其他類,這些類會被逐步加載,jar包和war包中的類並不是一次性都加載出來的,而是真正使用到這個類的時候纔會進行加載
package com.muzili.jvm;
public class TestDynamicLoad {
public static String initDate="test";
static {
System.out.println("運行TestDynamicLoad類的靜態方法.....");
}
public TestDynamicLoad(){
System.out.println("運行TestDynamicLoad類的構造方法.....");
}
public static class InnerClassA{
static {
System.out.println("運行InnerClassA類的靜態方法.....");
}
public InnerClassA(){
System.out.println("運行InnerClassA類的構造方法.....");
}
}
public static void main(String[] args) {
TestDynamicLoad testDynamicLoad=new TestDynamicLoad();
InnerClassA innerClassA=new InnerClassA();
}
}
結果輸出:
運行TestDynamicLoad類的靜態方法.....
運行TestDynamicLoad類的構造方法.....
運行InnerClassA類的靜態方法.....
運行InnerClassA類的構造方法.....
Process finished with exit code 0
1.2 類加載器和雙親委派機制
1.2.1 類加載器
Java中的類加載器主要有三種:BootstrapClassLoader(引導類加載器)
、ExtClassLoader(擴展類加載器)
、AppClassLoader(應用程序類加載器)
。
- BootstrapClassLoader:主要用於加載Java程序運行時必不可少的核心類,如
jre/lib/rt.jar
、jre/lib/charsets.jar
中的類。 - ExtClassLoader:用於加載
jre/lib/ext
下的所有jar包中的類 - AppClassLoader:用於加載自己編寫的類
package com.muzili.jvm;
import com.sun.crypto.provider.DESKeyFactory;
import sun.misc.Launcher;
import java.net.URL;
public class TestJDKClassLoader {
public static void main(String[] args) {
/**
* 輸出以下類的ClassLoader
*/
System.out.println(String.class.getClassLoader());
System.out.println(DESKeyFactory.class.getClassLoader());
System.out.println(TestJDKClassLoader.class.getClassLoader());
System.out.println();
/**
* 獲取ClassLoader
*/
//因爲BootStrapClassLoader是通過C++實現,並實例化的故Java程序無法獲取
ClassLoader appClassLoader=TestJDKClassLoader.class.getClassLoader();
ClassLoader extClassLoader=appClassLoader.getParent();
ClassLoader bootstrapClassLoader=extClassLoader.getParent();
System.out.println("the BootStrapClassLoader:"+bootstrapClassLoader);
System.out.println("the ExtClassLoader:"+extClassLoader);
System.out.println("the AppClassLoader:"+appClassLoader);
System.out.println();
URL[] bootStrapUrls = Launcher.getBootstrapClassPath().getURLs();
System.out.println("BootStrapLoader加載:");
for (URL url : bootStrapUrls) {
System.out.println(url.getPath());
}
System.out.println();
System.out.println("ExtClassLoader加載:");
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println();
System.out.println("AppClassLoader加載:");
System.out.println(System.getProperty("java.class.path"));
}
}
結果輸出:
null
sun.misc.Launcher$ExtClassLoader@4b67cf4d
sun.misc.Launcher$AppClassLoader@18b4aac2
the BootStrapClassLoader:null
the ExtClassLoader:sun.misc.Launcher$ExtClassLoader@4b67cf4d
the AppClassLoader:sun.misc.Launcher$AppClassLoader@18b4aac2
BootStrapLoader加載:
/E:/Program%20Files/Java/jdk1.8.0_191/jre/lib/resources.jar
/E:/Program%20Files/Java/jdk1.8.0_191/jre/lib/rt.jar
/E:/Program%20Files/Java/jdk1.8.0_191/jre/lib/sunrsasign.jar
/E:/Program%20Files/Java/jdk1.8.0_191/jre/lib/jsse.jar
/E:/Program%20Files/Java/jdk1.8.0_191/jre/lib/jce.jar
/E:/Program%20Files/Java/jdk1.8.0_191/jre/lib/charsets.jar
/E:/Program%20Files/Java/jdk1.8.0_191/jre/lib/jfr.jar
/E:/Program%20Files/Java/jdk1.8.0_191/jre/classes
ExtClassLoader加載:
E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext
AppClassLoader加載:
E:\Program Files\Java\jdk1.8.0_191\jre\lib\charsets.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\deploy.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\access-bridge-64.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\cldrdata.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\dnsns.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\jaccess.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\jfxrt.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\localedata.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\nashorn.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunec.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunjce_provider.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunmscapi.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunpkcs11.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\zipfs.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\javaws.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\jce.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\jfr.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\jfxswt.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\jsse.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\management-agent.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\plugin.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\resources.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\rt.jar;D:\workspace\tulingxueyuan-demo\out\production\jvm-demo-01;E:\Program Files\JetBrains\IntelliJ IDEA 2019.1.1\lib\idea_rt.jar
Process finished with exit code 0
類加載過程解析:
- 通過jvm創建
BootstrapClassLoader
實例,調用sun.misc.Launcher#getLauncher()
方法獲取啓動器對象【這裏使用到了設計模式中的單例模式】 - Launcher類的構造方法中,創建了兩個類加載器:
sun.misc.Launcher.ExtClassLoader#getExtClassLoader
[擴展類加載器]、sun.misc.Launcher.AppClassLoader#getAppClassLoader
[應用程序類加載器] - JVM默認會使用Launcher的
getClassLoader()
方法返回的類加載器AppClassLoader用於加載我們的類
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package sun.misc;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.nio.file.Paths;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.PermissionCollection;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.ProtectionDomain;
import java.security.cert.Certificate;
import java.util.HashSet;
import java.util.StringTokenizer;
import java.util.Vector;
import sun.net.www.ParseUtil;
public class Launcher {
private static URLStreamHandlerFactory factory = new Launcher.Factory();
private static Launcher launcher = new Launcher();
private static String bootClassPath = System.getProperty("sun.boot.class.path");
private ClassLoader loader;
private static URLStreamHandler fileHandler;
public static Launcher getLauncher() {
return launcher;
}
public Launcher() {
Launcher.ExtClassLoader var1;
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
Thread.currentThread().setContextClassLoader(this.loader);
String var2 = System.getProperty("java.security.manager");
if (var2 != null) {
SecurityManager var3 = null;
if (!"".equals(var2) && !"default".equals(var2)) {
try {
var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
} catch (IllegalAccessException var5) {
} catch (InstantiationException var6) {
} catch (ClassNotFoundException var7) {
} catch (ClassCastException var8) {
}
} else {
var3 = new SecurityManager();
}
if (var3 == null) {
throw new InternalError("Could not create SecurityManager: " + var2);
}
System.setSecurityManager(var3);
}
}
public ClassLoader getClassLoader() {
return this.loader;
}
//省略一些其他代碼
}
1.2.2 雙親委派機制
雙親委派機制,就是在加載類的時候,級別較低的類加載器首先會委託它的父級類加載器進行加載,如果父級類加載器加載不到纔會交給當前類加載器進行加載。
雙親委派機制有什麼意義:
- 沙箱安全:它可以保證java中的核心類不會被用戶定義的類給覆蓋,從而保證了系統的安全
- 防止類的重複加載:每個類加載器,都實現了緩存機制,當類第一次被加載之後就會被緩存起來,當再次加載的時候,就不用再每次都按照雙親委派機制來加載類,這樣可以提升類的加載效率
1.3 自定義類加載器
爲什麼需要自定義類加載器?
如果在實際開發中,可能有時我們想要加載一些外部目錄下的類,這個時候jvm提供的類加載器就無法滿足我們的需求了,這個時候我們需要自定義類加載器,用以加載外部目錄中的類。
1.3.1 定義一個簡單的類加載器
package com.muzili.jvm;
import java.io.FileInputStream;
import java.io.IOException;
public class MyClassLoaderTest {
public static class MyClassLoader extends ClassLoader{
private String clasasPath;
public MyClassLoader(String clasasPath) {
this.clasasPath = clasasPath;
}
private byte[] loadByte(String name) throws IOException {
name = name.replace(".", "/");
FileInputStream fis=new FileInputStream(clasasPath+"/"+name+".class");
int len=fis.available();
byte[] bytes=new byte[len];
fis.read(bytes);
fis.close();
return bytes;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] bytes = this.loadByte(name);
return defineClass(name,bytes,0,bytes.length);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
public static void main(String[] args) {
//初始化自定義的類加載器
MyClassLoader myClassLoader=new MyClassLoader("D:\\test");
try {
Class<?> clazz = myClassLoader.loadClass("com.muzili.jvm.User1");
System.out.println(clazz.getClassLoader());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
運行結果:
com.muzili.jvm.MyClassLoaderTest$MyClassLoader@4554617c
Process finished with exit code 0
1.3.2 打破類加載器的雙親委派機制
package com.muzili.jvm;
import java.io.FileInputStream;
import java.io.IOException;
public class MyClassLoaderTest2 {
public static class MyClassLoader extends ClassLoader{
private String clasasPath;
public MyClassLoader(String clasasPath) {
this.clasasPath = clasasPath;
}
private byte[] loadByte(String name) throws IOException {
name = name.replace(".", "/");
FileInputStream fis=new FileInputStream(clasasPath+"/"+name+".class");
int len=fis.available();
byte[] bytes=new byte[len];
fis.read(bytes);
fis.close();
return bytes;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] bytes = this.loadByte(name);
return defineClass(name,bytes,0,bytes.length);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 打破雙親委派機制
* @param name
* @return
* @throws ClassNotFoundException
*/
@Override
public 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();
//直接將雙親委派機制取消不做任何處理會導致java運行時非常重要的類無法加載
if (!name.startsWith("com.muzili.jvm")){
c=this.getParent().loadClass(name);
}
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
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
}
public static void main(String[] args) {
//初始化自定義的類加載器
MyClassLoader myClassLoader=new MyClassLoader("D:\\test");
try {
Class<?> clazz = myClassLoader.loadClass("com.muzili.jvm.User1");
System.out.println(clazz);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
結果輸出:
class com.muzili.jvm.User1