深入Java虛擬機(jī)JVM類加載初始化學(xué)習(xí)筆記
1. Classloader的作用,概括來(lái)說(shuō)就是將編譯后的class裝載、加載到機(jī)器內(nèi)存中,為了以后的程序的執(zhí)行提供前提條件。
2. 一段程序引發(fā)的思考:
風(fēng)中葉老師在他的視頻中給了我們一段程序,號(hào)稱是世界上所有的Java程序員都會(huì)犯的錯(cuò)誤。
詭異代碼如下:
Java代碼
- package test01;
- class Singleton {
- public static Singleton singleton = new Singleton();
- public static int a;
- public static int b = 0;
- private Singleton() {
- super();
- a++;
- b++;
- }
- public static Singleton GetInstence() {
- return singleton;
- }
- }
- public class MyTest {
- /**
- * @param args
- */
- public static void main(String[] args) {
- Singleton mysingleton = Singleton.GetInstence();
- System.out.println(mysingleton.a);
- System.out.println(mysingleton.b);
- }
- }
一般不假思索的結(jié)論就是,a=1,b=1。給出的原因是:a、b都是靜態(tài)變量,在構(gòu)造函數(shù)調(diào)用的時(shí)候已經(jīng)對(duì)a和b都加1了。答案就都是1。但是運(yùn)行完后答案卻是a=1,b=0。
下面我們將代碼稍微變一下
Java代碼
- public static Singleton singleton = new Singleton();
- public static int a;
- public static int b = 0;
的代碼部分替換成
Java代碼
- public static int a;
- public static int b = 0;
- public static Singleton singleton = new Singleton();
效果就是剛才預(yù)期的a=1,b=1。
為什么呢,這3句無(wú)非就是靜態(tài)變量的聲明、初始化,值的變化和聲明的順序還有關(guān)系嗎?Java不是面向?qū)ο蟮膯?怎么和結(jié)構(gòu)化的語(yǔ)言似地,順序還有關(guān)系。這個(gè)就是和Java虛擬機(jī)JVM加載類的原理有著直接的關(guān)系。
1. 類在JVM中的工作原理
要想使用一個(gè)Java類為自己工作,必須經(jīng)過(guò)以下幾個(gè)過(guò)程
1):類加載load:從字節(jié)碼二進(jìn)制文件——.class文件將類加載到內(nèi)存,從而達(dá)到類的從硬盤(pán)上到內(nèi)存上的一個(gè)遷移,所有的程序必須加載到內(nèi)存才能工作。將內(nèi)存中的class放到運(yùn)行時(shí)數(shù)據(jù)區(qū)的方法區(qū)內(nèi),之后在堆區(qū)建立一個(gè)java.lang.Class對(duì)象,用來(lái)封裝方法區(qū)的數(shù)據(jù)結(jié)構(gòu)。這個(gè)時(shí)候就體現(xiàn)出了萬(wàn)事萬(wàn)物皆對(duì)象了,干什么事情都得有個(gè)對(duì)象。就是到了***層究竟是雞生蛋,還是蛋生雞呢?類加載的最終產(chǎn)物就是堆中的一個(gè)java.lang.Class對(duì)象。
2):連接:連接又分為以下小步驟
驗(yàn)證:出于安全性的考慮,驗(yàn)證內(nèi)存中的字節(jié)碼是否符合JVM的規(guī)范,類的結(jié)構(gòu)規(guī)范、語(yǔ)義檢查、字節(jié)碼操作是否合法、這個(gè)是為了防止用戶自己建立一個(gè)非法的XX.class文件就進(jìn)行工作了,或者是JVM版本沖突的問(wèn)題,比如在JDK6下面編譯通過(guò)的class(其中包含注解特性的類),是不能在JDK1.4的JVM下運(yùn)行的。
準(zhǔn)備:將類的靜態(tài)變量進(jìn)行分配內(nèi)存空間、初始化默認(rèn)值。(對(duì)象還沒(méi)生成呢,所以這個(gè)時(shí)候沒(méi)有實(shí)例變量什么事情)
解析:把類的符號(hào)引用轉(zhuǎn)為直接引用(保留)
3):類的初始化: 將類的靜態(tài)變量賦予正確的初始值,這個(gè)初始值是開(kāi)發(fā)者自己定義時(shí)賦予的初始值,而不是默認(rèn)值。
2. 類的主動(dòng)使用與被動(dòng)使用
以下是視為主動(dòng)使用一個(gè)類,其他情況均視為被動(dòng)使用!
1):初學(xué)者最為常用的new一個(gè)類的實(shí)例對(duì)象(聲明不叫主動(dòng)使用)
2):對(duì)類的靜態(tài)變量進(jìn)行讀取、賦值操作的。
3):直接調(diào)用類的靜態(tài)方法。
4):反射調(diào)用一個(gè)類的方法。
5):初始化一個(gè)類的子類的時(shí)候,父類也相當(dāng)于被程序主動(dòng)調(diào)用了(如果調(diào)用子類的靜態(tài)變量是從父類繼承過(guò)來(lái)并沒(méi)有復(fù)寫(xiě)的,那么也就相當(dāng)于只用到了父類的東東,和子類無(wú)關(guān),所以這個(gè)時(shí)候子類不需要進(jìn)行類初始化)。
6):直接運(yùn)行一個(gè)main函數(shù)入口的類。
所有的JVM實(shí)現(xiàn)(不同的廠商有不同的實(shí)現(xiàn),有人就說(shuō)IBM的實(shí)現(xiàn)比Sun的要好……)在***主動(dòng)調(diào)用類和接口的時(shí)候才會(huì)初始化他們。


1. 類的加載方式
1):本地編譯好的class中直接加載
2):網(wǎng)絡(luò)加載:java.net.URLClassLoader可以加載url指定的類
3):從jar、zip等等壓縮文件加載類,自動(dòng)解析jar文件找到class文件去加載util類
4):從java源代碼文件動(dòng)態(tài)編譯成為class文件
2. 類加載器
JVM自帶的默認(rèn)加載器
1):根類加載器:bootstrap,由C++編寫(xiě),所有Java程序無(wú)法獲得。
2):擴(kuò)展類加載器:由Java編寫(xiě)。
3):系統(tǒng)類、應(yīng)用類加載器:由Java編寫(xiě)。
用戶自定義的類加載器:java.lang.ClassLoader的子類,用戶可以定制類的加載方式。每一個(gè)類都包含了加載他的ClassLoader的一個(gè)引用——getClass().getClassLoader()。如果返回的是null,證明加載他的ClassLoader是根加載器bootstrap。
如下代碼

這里面的指針就是C++的指針
1. 回顧那個(gè)詭異的代碼
從入口開(kāi)始看
Singleton mysingleton = Singleton.GetInstence();
是根據(jù)內(nèi)部類的靜態(tài)方法要一個(gè)Singleton實(shí)例。
這個(gè)時(shí)候就屬于主動(dòng)調(diào)用Singleton類了。
之后內(nèi)存開(kāi)始加載Singleton類
1):對(duì)Singleton的所有的靜態(tài)變量分配空間,賦默認(rèn)的值,所以在這個(gè)時(shí)候,singleton=null、a=0、b=0。注意b的0是默認(rèn)值,并不是咱們手工為其賦予的的那個(gè)0值。
2):之后對(duì)靜態(tài)變量賦值,這個(gè)時(shí)候的賦值就是我們?cè)诔绦蚶锸止こ跏蓟哪莻€(gè)值了。此時(shí)singleton = new Singleton();調(diào)用了構(gòu)造方法。構(gòu)造方法里面a=1、b=1。之后接著順序往下執(zhí)行。
3):
- public static int a;
- public static int b = 0;
a沒(méi)有賦值,保持原狀a=1。b被賦值了,b原先的1值被覆蓋了,b=0。所以結(jié)果就是這么來(lái)的。類中的靜態(tài)塊static塊也是順序地從上到下執(zhí)行的。
2. 編譯時(shí)常量、非編譯時(shí)常量的靜態(tài)變量
如下代碼
Java代碼
- package test01;
- class FinalStatic {
- public static final int A = 4 + 4;
- static {
- System.out.println("如果執(zhí)行了,證明類初始化了……");
- }
- }
- public class MyTest03 {
- /**
- * @param args
- */
- public static void main(String[] args) {
- System.out.println(FinalStatic.A);
- }
- }
結(jié)果是只打印出了8,證明類并沒(méi)有初始化。反編譯源碼發(fā)現(xiàn)class里面的內(nèi)容是
public static final int A = 8;
也就是說(shuō)編譯器很智能的、在編譯的時(shí)候自己就能算出4+4是8,是一個(gè)固定的數(shù)字。沒(méi)有什么未知的因素在里面。
將代碼稍微改一下
public static final int A = 4 + new Random().nextInt(10);
這個(gè)時(shí)候靜態(tài)塊就執(zhí)行了,證明類初始化了。在靜態(tài)final變量在編譯時(shí)不定的情況下。如果客戶程序這個(gè)時(shí)候訪問(wèn)了該類的靜態(tài)變量,那就會(huì)對(duì)類進(jìn)行初始化,所以盡量靜態(tài)final變量盡量沒(méi)什么可變因素在里面1,否則性能會(huì)有所下降。
1. ClassLoader的剖析
ClassLoader的loadClass方法加載一個(gè)類不屬于主動(dòng)調(diào)用,不會(huì)導(dǎo)致類的初始化。如下代碼塊
Java代碼
- ClassLoader classLoader = ClassLoader.getSystemClassLoader();
- Class> clazz = classLoader.loadClass("test01.ClassDemo");
并不會(huì)讓類加載器初始化test01.ClassDemo,因?yàn)檫@不屬于主動(dòng)調(diào)用此類。
ClassLoader的關(guān)系:
根加載器——》擴(kuò)展類加載器——》應(yīng)用類加載器——》用戶自定義類加載器
加載類的過(guò)程是首先從根加載器開(kāi)始加載、根加載器加載不了的,由擴(kuò)展類加載器加載,再加載不了的有應(yīng)用加載器加載,應(yīng)用加載器如果還加載不了就由自定義的加載器(一定繼承自java.lang. ClassLoader)加載、如果自定義的加載器還加載不了。而且下面已經(jīng)沒(méi)有再特殊的類加載器了,就會(huì)拋出ClassNotFoundException,表面上異常是類找不到,實(shí)際上是class加載失敗,更不能創(chuàng)建該類的Class對(duì)象。
若一個(gè)類能在某一層類加載器成功加載,那么這一層的加載器稱為定義類加載器。那么在這層類生成的Class引用返回下一層加載器叫做初始類加載器。因?yàn)榧虞d成功后返回一個(gè)Class引用給它的服務(wù)對(duì)象——也就是調(diào)用它的類加載器。考慮到安全,父委托加載機(jī)制。

ClassLoader加載類的原代碼如下
Java代碼
- protected synchronized Class> loadClass(String name, boolean resolve)
- throws ClassNotFoundException
- {
- // First, check if the class has already been loaded
- Class c = findLoadedClass(name);
- if (c == null) {
- 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.
- c = findClass(name);
- }
- }
- if (resolve) {
- resolveClass(c);
- }
- return c;
- }
初始化系統(tǒng)ClassLoader代碼如下
Java代碼
- private static synchronized void initSystemClassLoader() {
- if (!sclSet) {
- if (scl != null)
- throw new IllegalStateException("recursive invocation");
- sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
- if (l != null) {
- Throwable oops = null;
- scl = l.getClassLoader();
- try {
- PrivilegedExceptionAction a;
- a = new SystemClassLoaderAction(scl);
- scl = (ClassLoader) AccessController.doPrivileged(a);
- } catch (PrivilegedActionException pae) {
- oops = pae.getCause();
- if (oops instanceof InvocationTargetException) {
- oops = oops.getCause();
- }
- }
- if (oops != null) {
- if (oops instanceof Error) {
- throw (Error) oops;
- } else {
- // wrap the exception
- throw new Error(oops);
- }
- }
- }
- sclSet = true;
- }
- }
它里面調(diào)用了很多native的方法,也就是通過(guò)JNI調(diào)用底層C++的代碼。
當(dāng)一個(gè)類被加載、連接、初始化后,它的生命周期就開(kāi)始了,當(dāng)代表該類的Class對(duì)象不再被引用、即已經(jīng)不可觸及的時(shí)候,Class對(duì)象的生命周期結(jié)束。那么該類的方法區(qū)內(nèi)的數(shù)據(jù)也會(huì)被卸載,從而結(jié)束該類的生命周期。一個(gè)類的生命周期取決于它Class對(duì)象的生命周期。由Java虛擬機(jī)自帶的默認(rèn)加載器(根加載器、擴(kuò)展加載器、系統(tǒng)加載器)所加載的類在JVM生命周期中始終不被卸載。所以這些類的Class對(duì)象(我稱其為實(shí)例的模板對(duì)象)始終能被觸及!而由用戶自定義的類加載器所加載的類會(huì)被卸載掉!
【編輯推薦】