之前學JAVA高併發的時候有一定的瞭解過類加載機制。
那麼加載到方法區裏面去的呢?
一、類生命週期
我們得從類生命週期開始說起,類的生命週期總共分七歩,接下來我們先了解下從加載到初始化。如下圖
二、類加載器
在java中其實專門有一個東西負責裝入類,它叫做類加載器
類加載器負責裝入類,搜索網絡、jar、zip文件夾、二進制數據、內存類等指定位置的類資源。
一個java程序運行,至少需要三個類加載器實例,負責加載不同的類的加載,那怎麼去理解至少需要三個類加載器實例呢?
這個我覺得也很好理解,比如我們後端程序員不可能讓我們去做行政,程序員鼓勵師不能去敲代碼,每個人的工作分工不同,要各司其職,這樣的話才能更好的去維護,更好的去管理,在java中也是一樣的。如下圖
-
Bootstrap loader核心類庫加載器
核心類庫加載器是由C語言寫的,什麼叫核心類庫呢?如我們的Object它就是一個類,它就是用C語言寫的,爲什麼要用C語
言寫呢?因爲java虛擬機最開始是由C語言實現的,先有了C然後再有了java,少了它就無法運行的類就叫核心類庫,對應
JRE_HOME/jre/lib目錄 -
Extension Class loader擴展類庫加載器
擴展類庫加載器在新的版本中有一些調整,這個先放一邊,畢竟也不是很大的調整,它是專門加載JRE_HOME/jre/lib/ext
目錄下的擴展類庫的,什麼叫擴展類庫呢?就是說這個類可能在某些平臺上沒有的類,就是說不是缺它不可的類,比如
我們談戀愛,沒有女朋友不行,但是發現真的少了女朋友照樣過得很瀟灑,就是這麼一回事。 -
Appliction class loader用戶應用程序加載器
應用程序加載器是爲了加載我們開發人員寫的代碼,那它是怎麼知道我們寫的代碼在哪些位置呢?有一個java.class.path
參數,用戶應用程序加載器通過加載這個參數,就能得到對應的目錄
三、驗證問題
1、 如何查看類對應的加載器?
我們可以通過JDK-API查看:java.lang.Class.getClassloader()返回類的類加載器;如果這個類是BootstrapLoader
加載的,那麼這個方法在這種實現中就會返回null。
demo
/**
* 查看類的加載器實例
*/
public class ClassLoaderView {
public static void main(String[] args) throws Exception {
// 加載核心類庫的 BootStrap ClassLoader
System.out.println(" 核心類庫加載器:"
+ ClassLoaderView.class.getClassLoader().loadClass("java.lang.String").getClassLoader());
System.out.println(" 核心類庫加載器:"
+ ClassLoaderView.class.getClassLoader().loadClass("java.lang.Object").getClassLoader());
// 加載拓展庫的 Extension ClassLoader
System.out.println("拓展類庫加載器:" + ClassLoaderView.class.getClassLoader()
.loadClass("com.sun.nio.zipfs.ZipCoder").getClassLoader());
// 加載應用程序的
System.out.println("應用程序庫加載器:" + ClassLoaderView.class.getClassLoader());
// 雙親委派模型 Parents Delegation Model
System.out.println("應用程序庫加載器的父類:" + ClassLoaderView.class.getClassLoader().getParent());
System.out.println(
"應用程序庫加載器的父類的父類:" + ClassLoaderView.class.getClassLoader().getParent().getParent());
}
}
運行結果
核心類庫加載器:null
核心類庫加載器:null
拓展類庫加載器:sun.misc.Launcher$ExtClassLoader@3e3abc88
應用程序庫加載器:sun.misc.Launcher$AppClassLoader@18b4aac2
應用程序庫加載器的父類:sun.misc.Launcher$ExtClassLoader@3e3abc88
應用程序庫加載器的父類的父類:null
2、JVM如何知道我們的類在何方?
查找AppClassLoader可以看到如下代碼段,讀取了java.classs.path參數,所以發現jvm還是比較傻的。
還可以利用jps、jcmd兩個命令進行驗證.
一、使用jmcd命令進行驗證,運行如下代碼
package classloader_demo1;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
System.out.println("Hello World!");
System.in.read();//這段代碼會阻塞
}
}
打開命名窗口
然後運行jcmd help命令
找到對應main函數的ID運行:jcmd 17389 help看看支持哪些命令
可以看到有非常多的命令,可以看到有很多命令,GC.heap_dump可以查看堆信息、VM.flags可以看到jvm的參數配置等等…,這次我們就先看看jvm系統參數的配置,運行jcmd 17389 VM.system_propertites命令
如下圖可以看到自己類的信息有這麼多,那麼爲什麼有這麼多呢,其實是idea開發工具幫我們做了了很多的配置,有了這些配置信息jvm就能找到我們程序編譯之後的目錄,進而運行我們的代碼。
3、類會不會重複加載?
那麼類會不會重複加載呢?答案肯定是否定的。
類不會重複加載
類的唯一性:同一個加載器,類名一樣,代表是同一個類。
識別方式:Classloader Insance id + PackgeName + ClassName
驗證方式:使用類加載器,對同一個class類的不同版本,進行多次加載,檢查是否會加載到最新的代碼。
①、新建HelloService類加載測試類
/** 類加載測試類 */
public class HelloService {
public static String value = getValue();
static {
System.out.println("靜態代碼塊運行");
}
private static String getValue() {
System.out.println("靜態方法被運行");
return "netease";
}
public void test() {
System.out.println("hello.." + value);
}
}
②、新建LoaderTest類
package classloader_demo1;
import java.net.URL;
import java.net.URLClassLoader;
/**
* 指定class 進行加載
*/
public class LoaderTest {
public static void main(String[] args) throws Exception {
//指定jvm查找類的位置
URL classUrl = new URL("file:///ideaWorkspace/");
URLClassLoader loader = new URLClassLoader(new URL[]{classUrl});
while (true) {
if(loader == null){
break;
}
// 問題:靜態塊什麼時候觸發?
Class clazz = loader.loadClass("HelloService");
System.out.println("HelloService所使用的類加載器:" + clazz.getClassLoader());
//反射創建對象
Object newInstance = clazz.newInstance();
//調用test方法
Object value = clazz.getMethod("test").invoke(newInstance);
System.out.println("調用getValue獲得的返回值爲:" + value);
Thread.sleep(3000L);
System.out.println();
}
}
}
運行結果
HelloService所使用的類加載器:java.net.URLClassLoader@1c20c684
靜態方法被運行
靜態代碼塊運行
hello..netease
調用getValue獲得的返回值爲:null
HelloService所使用的類加載器:java.net.URLClassLoader@1218025c
靜態方法被運行
靜態代碼塊運行
hello..netease
調用getValue獲得的返回值爲:null
代碼是一直循環創建類加載器創建對象的,接下來我們修改HelloService中的代碼,加了幾個1
重新編譯
HelloService所使用的類加載器:java.net.URLClassLoader@1218025c
靜態方法被運行
靜態代碼塊運行
hello..netease
調用getValue獲得的返回值爲:null
運行結果還是一樣,進一步的證明了類不會重複加載
4、類如何卸載?
類被卸載需要滿足兩個條件
①:該Class所有的實例都已經被GC銷燬;
②:加載該類的所有ClassLoader實例都已經被GC;
驗證方式:jvm啓動參數中增加-verbose:class參數,輸出加載和卸載的日誌信息,同手手動的觸發GC的操作
下面開始進行驗證:
首先給我們運行的類加上-verbose:class參數,加上字後就能看到日誌信息了
修改LoaderTest代碼
package classloader_demo1;
import java.net.URL;
import java.net.URLClassLoader;
/**
* 指定class 進行加載
*/
public class LoaderTest {
public static void main(String[] args) throws Exception {
//指定jvm查找類的位置
URL classUrl = new URL("file:///ideaWorkspace/");
URLClassLoader loader = new URLClassLoader(new URL[]{classUrl});
while (true) {
// 通過URLClassLoader創建一個新的類加載器
if (loader == null) {
break;
}
// 問題:靜態塊什麼時候觸發?
Class clazz = loader.loadClass("HelloService");
System.out.println("HelloService所使用的類加載器:" + clazz.getClassLoader());
//反射創建對象
Object newInstance = clazz.newInstance();
//調用test方法
Object value = clazz.getMethod("test").invoke(newInstance);
System.out.println("調用getValue獲得的返回值爲:" + value);
Thread.sleep(3000L);
System.out.println();
//對象置爲空方便GC直接回收
newInstance = null;
//類加載器也置爲空
loader = null;
}
//手動觸發GC,此方法不一定有用,但能讓jvm更主動的去做一次GC
System.gc();
Thread.sleep(10000L);
}
}
可以看到信息已經打印出來了,說明Class所有的實例和該類的所有ClassLoader實例都已經被GC;
5、雙親委派模型是什麼?
首先要了解什麼是雙親委派模型?
爲了避免重複加載,由下到上逐級委託,由上倒下逐級查找
首先不會自己去嘗試加載類,而是把這個請求,委派給父加載器去完成;每一個層次的加載器都是如此,因此所有的類加載請求都會傳給上層的啓動類加載。
只有當父加載器反饋自己無法完成該加載的請求時(該加載器的搜索範圍中沒有找到對應的類),子加載器纔會嘗試自己去加載。
==注:類加載器之間不存在父類子類的關係,“雙親”是翻譯,可以理解爲邏輯定義的上下級關係。==
下面用代碼具體去體現
首先看一個問題,如果我把類加載器的創建放到while循環中去,每次都會創建新的類加載器,那麼能不能實現類的動態加載呢?
package classloader_demo1;
import java.net.URL;
import java.net.URLClassLoader;
/**
* 指定class 進行加載
*/
public class LoaderTest {
public static void main(String[] args) throws Exception {
//指定jvm查找類的位置
URL classUrl = new URL("file:///ideaWorkspace/");
while (true) {
//每次都新建一個類加載器
URLClassLoader loader = new URLClassLoader(new URL[]{classUrl});
// 通過URLClassLoader創建一個新的類加載器
if (loader == null) {
break;
}
// 問題:靜態塊什麼時候觸發?
Class clazz = loader.loadClass("HelloService");
System.out.println("HelloService所使用的類加載器:" + clazz.getClassLoader());
//反射創建對象
Object newInstance = clazz.newInstance();
//調用test方法
Object value = clazz.getMethod("test").invoke(newInstance);
System.out.println("調用getValue獲得的返回值爲:" + value);
Thread.sleep(3000L);
System.out.println();
//對象置爲空方便GC直接回收
newInstance = null;
//類加載器也置爲空
loader = null;
}
//手動觸發GC,此方法不一定有用,但能讓jvm更主動的去做一次GC
System.gc();
Thread.sleep(10000L);
}
}
HelloService所使用的類加載器:java.net.URLClassLoader@119d7047
靜態方法被運行
靜態代碼塊運行
hello..4444netease
調用getValue獲得的返回值爲:null
[Loaded HelloService from file:/ideaWorkspace/]
HelloService所使用的類加載器:java.net.URLClassLoader@3b07d329
靜態方法被運行
靜態代碼塊運行
hello..5555netease
調用getValue獲得的返回值爲:null
可以看到編譯之後,類確實被重新加載了,eclipse/idea的熱部署和jsp頁面就是通過不同的類加載器實現的。
敲重點!!!下面思考一個問題,如果我新創建一個parentLoader,然後傳入while循環中的類加載器,還能不能實現動態加載?
package classloader_demo1;
import java.net.URL;
import java.net.URLClassLoader;
/**
* 指定class 進行加載
*/
public class LoaderTest {
public static void main(String[] args) throws Exception {
//指定jvm查找類的位置
URL classUrl = new URL("file:///ideaWorkspace/");
URLClassLoader parentLoader = new URLClassLoader(new URL[]{classUrl});
while (true) {
//每次都新建一個類加載器
URLClassLoader loader = new URLClassLoader(new URL[]{classUrl},parentLoader);
// 通過URLClassLoader創建一個新的類加載器
if (loader == null) {
break;
}
// 問題:靜態塊什麼時候觸發?
Class clazz = loader.loadClass("HelloService");
System.out.println("HelloService所使用的類加載器:" + clazz.getClassLoader());
//反射創建對象
Object newInstance = clazz.newInstance();
//調用test方法
Object value = clazz.getMethod("test").invoke(newInstance);
System.out.println("調用getValue獲得的返回值爲:" + value);
Thread.sleep(3000L);
System.out.println();
//對象置爲空方便GC直接回收
newInstance = null;
//類加載器也置爲空
loader = null;
}
//手動觸發GC,此方法不一定有用,但能讓jvm更主動的去做一次GC
System.gc();
Thread.sleep(10000L);
}
}
答案是否定的,其實這裏傳入parentLoader就是使用了委派雙親模式,雖然每次都新創建了一個類加載器,但是lodaer不會自己去查找類,而是會把查找交給parentLoader去查找,所以每次使用的還是parentLoader類加載器,也就不會動態的加載HelloService類了。
如果我把parentLoader刪掉,其實他指定的父級加載器就會是ExtClassLoader/AppclassLoader,希望大家能夠理解,有問題的話可以隨時私信我進行溝通。
四、總結:
今天我們學習了整個類加載器的相關知識,學習了類的生命週期、加載順序,學會了
1、如何查看類加載器
2、JVM如何知道我們的類在何方?
3、類會不會重複加載?
4、類如何卸載條件是什麼?
5、雙親委派模型是什麼?
最後希望大家能夠多多交流,一起學習一起進步!