JVM類加載機制
首先我們的java小程式demo,經過編譯后變成.class檔案,他是如何加載到記憶體的將.class檔案
記憶體中有兩大物件:1.類的位元組碼物件,只有一份在記憶體,2.類物件會有多份
文章目錄
- JVM類加載機制
- 前言
- 一、類加載運行全程序
- 1.咱們先看圖:
- 2.其中loadClass的類加載程序有如下幾步:
- 加載:
- 驗證:
- 準備:
- 決議:
- 初始化:
- 二、類加載器和雙親委派機制
- 1.Java里有如下幾種類加載器
- 看一個類加載器示例:
- 類加載器初始化程序:
- 如下原始碼:
- 2.雙親委派機制
- 為什么要設計雙親委派機制?
- 沙箱安全機制:
- 避免類的重復加載:
- 看一個類加載示例:
- 自定義類加載器:
- 打破雙親委派機制
前言
通過Java命令執行代碼的大體流程如下:
一、類加載運行全程序
當我們用java命令運行某個類的main函式啟動程式時,首先需要通過類加載器把主類加載到 JVM
1.咱們先看圖:
先不做解釋!

2.其中loadClass的類加載程序有如下幾步:
加載 >> 驗證 >> 準備 >> 決議 >> 初始化 >> 使用 >> 卸載
加載:
在硬碟上查找并通過IO讀入位元組碼檔案,使用到類時才會加載,例如呼叫類的 main()方法,new物件等等,在加載階段會在記憶體中生成一個代表這個類的 java.lang.Class物件,作為方法區這個類的各種資料的訪問入口
驗證:
校驗位元組碼檔案的正確性,也就是驗證位元組碼是否格式正確
準備:
給類的靜態變數分配記憶體,并賦予默認值,成員變數一開始是默認值例如 int i=0;String s=null;如果被final修飾的直接賦值不會有默認值的
決議:
將符號參考替換為直接參考,該階段會把一些靜態方法(符號參考,比如 main()方法)替換為指向資料所存記憶體的指標或句柄等(直接參考),這是所謂的靜態鏈接過 程(類加載期間完成),動態鏈接是在程式運行期間完成的將符號參考替換為直接參考,動態鏈接先不說
初始化:
對類的靜態變數初始化為指定的值,執行靜態代碼塊,

類被加載到方法區中后主要包含 運行時常量池、型別資訊、欄位資訊、方法資訊、類加載器的 參考、對應class實體的參考等資訊, 類加載器的參考:這個類到類加載器實體的參考 對應class實體的參考:類加載器在加載類資訊放到方法區中后,會創建一個對應的Class 型別的 物件實體放到堆(Heap)中, 作為開發人員訪問方法區中類定義的入口和切入點,
注意:
主類在運行程序中如果使用到其它類,會逐步加載這些類, jar包或war包里的類不是一次性全部加載的,是使用到時才加載,
public class TestDynamicLoad {
static {
System.out.println("*************load TestDynamicLoad************");
}
public static void main(String[] args) {
new A();
System.out.println("*************load test************");
B b = null;
//new B(); 這塊打開就是加載B
}
}
class A {
static {
System.out.println("*************load A************");
}
public A() {
System.out.println("*************initial A************");
}
}
class B {
static {
System.out.println("*************load B************");
}
public B() {
System.out.println("*************initial B************");
}
}
列印結果:
*************load TestDynamicLoad************
*************load A************
*************initial A************
*************load test************
二、類加載器和雙親委派機制
1.Java里有如下幾種類加載器
引導類加載器:負責加載支撐JVM運行的位于JRE的lib目錄下的核心類別庫,比如 rt.jar、charsets.jar等 擴展類加載器:負責加載支撐JVM運行的位于JRE的lib目錄下的ext擴展目錄中的JAR 類包
應用程式類加載器:負責加載ClassPath路徑下的類包,主要就是加載你自己寫的那些類
自定義加載器:負責加載用戶自定義路徑下的類包
看一個類加載器示例:
public class TestJDKClassLoader {
public static void main(String[] args) {
System.out.println(String.class.getClassLoader());
System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());
System.out.println(TestJDKClassLoader.class.getClassLoader().getClass().getName());
System.out.println();
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
ClassLoader extClassloader = appClassLoader.getParent();
ClassLoader bootstrapLoader = extClassloader.getParent();
System.out.println("the bootstrapLoader : " + bootstrapLoader);
System.out.println("the extClassloader : " + extClassloader);
System.out.println("the appClassLoader : " + appClassLoader);
System.out.println();
System.out.println("bootstrapLoader加載以下檔案:");
URL[] urls = Launcher.getBootstrapClassPath().getURLs();
for (int i = 0; i < urls.length; i++) {
System.out.println(urls[i]);
}
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
sun.misc.Launcher$AppClassLoader
the bootstrapLoader : null
the extClassloader : sun.misc.Launcher$ExtClassLoader@7dc36524
the appClassLoader : sun.misc.Launcher$AppClassLoader@18b4aac2
bootstrapLoader加載以下檔案:
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/resources.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/rt.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/sunrsasign.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/jsse.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/jce.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/charsets.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/jfr.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/classes
extClassloader加載以下檔案:
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext;C:\Windows\Sun\Java\lib\ext
appClassLoader加載以下檔案:
C:\Program Files\Java\jdk1.8.0_181\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar;E:\code\mashibing-learning\JVM\target\classes;D:\IntelliJ IDEA 2019.3.3\lib\idea_rt.jar;C:\Users\capitek\.IntelliJIdea2019.3\system\captureAgent\debugger-agent.jar
類加載器初始化程序:
參見類運行加載全程序圖可知其中會創建JVM啟動器實體sun.misc.Launcher, sun.misc.Launcher初始化使用了單例模式設計,保證一個JVM虛擬機內只有一個 sun.misc.Launcher實體,
在Launcher構造方法內部,其創建了兩個類加載器,分別是 sun.misc.Launcher.ExtClassLoader(擴展類加載器)和sun.misc.Launcher.AppClassLoader(應用類加載器),
JVM默認使用Launcher的getClassLoader()方法回傳的類加載器AppClassLoader的實體加載我們的應用程式,
如下原始碼:
public Launcher() {
Launcher.ExtClassLoader var1;
try {
//構造擴展類加載器,在構造的程序中將其父加載器設定為null
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
//構造應用類加載器,在構造的程序中將其父加載器設定為ExtClassLoader,
//Launcher的loader屬性值是AppClassLoader,我們一般都是用這個類加載器來加載我們自 己寫的應用程式
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");
}
2.雙親委派機制
JVM類加載器是有親子層級結構的,如下圖

這里類加載其實就有一個雙親委派機制,加載某個類時會先委托父加載器尋找目標類,找不到再 委托上層父加載器加載,如果所有父加載器在自己的加載類路徑下都找不到目標類,則在自己的 類加載路徑中查找并載入目標類, 比如我們的Math類,最先會找應用程式類加載器加載,應用程式類加載器會先委托擴展類加載 器加載,擴展類加載器再委托引導類加載器,頂層引導類加載器在自己的類加載路徑里找了半天 沒找到Math類,則向下退回加載Math類的請求,擴展類加載器收到回復就自己加載,在自己的 類加載路徑里找了半天也沒找到Math類,又向下退回Math類的加載請求給應用程式類加載器, 應用程式類加載器于是在自己的類加載路徑里找Math類,結果找到了就自己加載了,, 雙親委派機制說簡單點就是,先找父親加載,不行再由兒子自己加載
我們來看下應用程式類加載器AppClassLoader加載類的雙親委派機制原始碼,AppClassLoader 的loadClass方法最侄訓呼叫其父類ClassLoader的loadClass方法,該方法的大體邏輯如下:
1. 首先,檢查一下指定名稱的類是否已經加載過,如果加載過了,就不需要再加載,直接 回傳,
2. 如果此類沒有加載過,那么,再判斷一下是否有父加載器;如果有父加載器,則由父加 載器加載(即呼叫parent.loadClass(name, false);).或者是呼叫bootstrap類加載器來加 載,
3. 如果父加載器及bootstrap類加載器都沒有找到指定的類,那么呼叫當前類加載器的 findClass方法來完成類加載,
//ClassLoader的loadClass方法,里面實作了雙親委派機制
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 檢查當前類加載器是否已經加載了該類
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {//如果當前加載器父加載器不為空則委托父加載器加載該類
c = parent.loadClass(name, false);
} else { //如果當前加載器父加載器為空則委托引導類加載器加載該類
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//都會呼叫URLClassLoader的findClass方法在加載器的類路徑里查找并加載該類
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;
}
}
為什么要設計雙親委派機制?
沙箱安全機制:
自己寫的java.lang.String.class類不會被加載,這樣便可以防止核心 API庫被隨意篡改
避免類的重復加載:
當父親已經加載了該類時,就沒有必要子ClassLoader再加載一 次,保證被加載類的唯一性
看一個類加載示例:
package java.lang;
public class String {
public static void main(String[] args) {
System.out.println("**************My String Class**************");
}
}
結果:
錯誤: 在類 java.lang.String 中找不到 main 方法, 請將 main 方法定義為:
public static void main(String[] args)
否則 JavaFX 應用程式類必須擴展javafx.application.Application
全盤負責委托機制 “全盤負責”是指當一個ClassLoder裝載一個類時,除非顯示的使用另外一個ClassLoder,該類 所依賴及參考的類也由這個ClassLoder載入,
自定義類加載器:
public class MyClassLoaderTest {
static class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
//defineClass將一個位元組陣列轉為Class物件,這個位元組陣列是class檔案讀取后最終的位元組 陣列,
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
}
public static void main(String args[]) throws Exception {
//初始化自定義類加載器,會先初始化父類ClassLoader,其中會把自定義類加載器的父加載 器設定為應用程式類加載器
MyClassLoader classLoader = new MyClassLoader("C:/a");
//D盤創建 test/com/qjc/clsload 幾級目錄,將User類的復制類User.class丟入該目錄
Class clazz = classLoader.loadClass("com.qjc.clsload.User");
Object obj = clazz.newInstance();
Method method = clazz.getDeclaredMethod("mm", null);
method.invoke(obj, null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
}
結果
運行結果: =======自己的加載器加載類呼叫方法=======
com.qjc.clsload.MyClassLoaderTest$MyClassLoader
打破雙親委派機制
再來一個沙箱安全機制示例,嘗試打破雙親委派機制,用自定義類加載器加載我們自己實作的 java.lang.String.class
public class MyClassLoaderTest {
static class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
//defineClass將一個位元組陣列轉為Class物件,這個位元組陣列是class檔案讀取后最終的位元組 陣列,
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
protected 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();
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[]) throws Exception {
MyClassLoader classLoader = new MyClassLoader("E:\\code\\learning\\JVM\\target\\classes");
//嘗試用自己改寫類加載機制去加載自己寫的java.lang.String.class
Class clazz = classLoader.loadClass("java.lang.String");
Object obj = clazz.newInstance();
Method method= clazz.getDeclaredMethod("sout", null);
method.invoke(obj, null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
}
結果
java.lang.SecurityException: Prohibited package name: java.lang
at java.lang.ClassLoader.preDefineClass(ClassLoader.java:662)
at java.lang.ClassLoader.defineClass(ClassLoader.java:761)
at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
at com.qjc.clsload.MyClassLoaderTest$MyClassLoader.findClass(MyClassLoaderTest.java:29)
at com.qjc.clsload.MyClassLoaderTest$MyClassLoader.loadClass(MyClassLoaderTest.java:49)
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/271319.html
標籤:java
下一篇:ArrayList去除重復元素
