如何深入理解Java虛擬機JVM類加載初始化,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。
成都創(chuàng)新互聯(lián)公司一直在為企業(yè)提供服務(wù),多年的磨煉,使我們在創(chuàng)意設(shè)計,成都全網(wǎng)營銷推廣到技術(shù)研發(fā)擁有了開發(fā)經(jīng)驗。我們擅長傾聽企業(yè)需求,挖掘用戶對產(chǎn)品需求服務(wù)價值,為企業(yè)制作有用的創(chuàng)意設(shè)計體驗。核心團隊擁有超過10年以上行業(yè)經(jīng)驗,涵蓋創(chuàng)意,策化,開發(fā)等專業(yè)領(lǐng)域,公司涉及領(lǐng)域有基礎(chǔ)互聯(lián)網(wǎng)服務(wù)綿陽電信機房機柜租用、成都App定制開發(fā)、手機移動建站、網(wǎng)頁設(shè)計、網(wǎng)絡(luò)整合營銷。
1. Classloader的作用,概括來說就是將編譯后的class裝載、加載到機器內(nèi)存中,為了以后的程序的執(zhí)行提供前提條件。
2. 一段程序引發(fā)的思考:
風中葉老師在他的視頻中給了我們一段程序,號稱是世界上所有的Java程序員都會犯的錯誤。
詭異代碼如下:
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)用的時候已經(jīng)對a和b都加1了。答案就都是1。但是運行完后答案卻是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句無非就是靜態(tài)變量的聲明、初始化,值的變化和聲明的順序還有關(guān)系嗎?Java不是面向?qū)ο蟮膯?怎么和結(jié)構(gòu)化的語言似地,順序還有關(guān)系。這個就是和Java虛擬機JVM加載類的原理有著直接的關(guān)系。
1. 類在JVM中的工作原理
要想使用一個Java類為自己工作,必須經(jīng)過以下幾個過程
1):類加載load:從字節(jié)碼二進制文件——.class文件將類加載到內(nèi)存,從而達到類的從硬盤上到內(nèi)存上的一個遷移,所有的程序必須加載到內(nèi)存才能工作。將內(nèi)存中的class放到運行時數(shù)據(jù)區(qū)的方法區(qū)內(nèi),之后在堆區(qū)建立一個java.lang.Class對象,用來封裝方法區(qū)的數(shù)據(jù)結(jié)構(gòu)。這個時候就體現(xiàn)出了萬事萬物皆對象了,干什么事情都得有個對象。就是到了***層究竟是雞生蛋,還是蛋生雞呢?類加載的最終產(chǎn)物就是堆中的一個java.lang.Class對象。
2):連接:連接又分為以下小步驟
驗證:出于安全性的考慮,驗證內(nèi)存中的字節(jié)碼是否符合JVM的規(guī)范,類的結(jié)構(gòu)規(guī)范、語義檢查、字節(jié)碼操作是否合法、這個是為了防止用戶自己建立一個非法的XX.class文件就進行工作了,或者是JVM版本沖突的問題,比如在JDK6下面編譯通過的class(其中包含注解特性的類),是不能在JDK1.4的JVM下運行的。
準備:將類的靜態(tài)變量進行分配內(nèi)存空間、初始化默認值。(對象還沒生成呢,所以這個時候沒有實例變量什么事情)
解析:把類的符號引用轉(zhuǎn)為直接引用(保留)
3):類的初始化: 將類的靜態(tài)變量賦予正確的初始值,這個初始值是開發(fā)者自己定義時賦予的初始值,而不是默認值。
2. 類的主動使用與被動使用
以下是視為主動使用一個類,其他情況均視為被動使用!
1):初學者最為常用的new一個類的實例對象(聲明不叫主動使用)
2):對類的靜態(tài)變量進行讀取、賦值操作的。
3):直接調(diào)用類的靜態(tài)方法。
4):反射調(diào)用一個類的方法。
5):初始化一個類的子類的時候,父類也相當于被程序主動調(diào)用了(如果調(diào)用子類的靜態(tài)變量是從父類繼承過來并沒有復(fù)寫的,那么也就相當于只用到了父類的東東,和子類無關(guān),所以這個時候子類不需要進行類初始化)。
6):直接運行一個main函數(shù)入口的類。
所有的JVM實現(xiàn)(不同的廠商有不同的實現(xiàn),有人就說IBM的實現(xiàn)比Sun的要好……)在***主動調(diào)用類和接口的時候才會初始化他們。
1. 類的加載方式
1):本地編譯好的class中直接加載
2):網(wǎng)絡(luò)加載:java.net.URLClassLoader可以加載url指定的類
3):從jar、zip等等壓縮文件加載類,自動解析jar文件找到class文件去加載util類
4):從java源代碼文件動態(tài)編譯成為class文件
2. 類加載器
JVM自帶的默認加載器
1):根類加載器:bootstrap,由C++編寫,所有Java程序無法獲得。
2):擴展類加載器:由Java編寫。
3):系統(tǒng)類、應(yīng)用類加載器:由Java編寫。
用戶自定義的類加載器:java.lang.ClassLoader的子類,用戶可以定制類的加載方式。每一個類都包含了加載他的ClassLoader的一個引用——getClass().getClassLoader()。如果返回的是null,證明加載他的ClassLoader是根加載器bootstrap。
如下代碼
這里面的指針就是C++的指針
1. 回顧那個詭異的代碼
從入口開始看
Singleton mysingleton = Singleton.GetInstence();
是根據(jù)內(nèi)部類的靜態(tài)方法要一個Singleton實例。
這個時候就屬于主動調(diào)用Singleton類了。
之后內(nèi)存開始加載Singleton類
1):對Singleton的所有的靜態(tài)變量分配空間,賦默認的值,所以在這個時候,singleton=null、a=0、b=0。注意b的0是默認值,并不是咱們手工為其賦予的的那個0值。
2):之后對靜態(tài)變量賦值,這個時候的賦值就是我們在程序里手工初始化的那個值了。此時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沒有賦值,保持原狀a=1。b被賦值了,b原先的1值被覆蓋了,b=0。所以結(jié)果就是這么來的。類中的靜態(tài)塊static塊也是順序地從上到下執(zhí)行的。
2. 編譯時常量、非編譯時常量的靜態(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,證明類并沒有初始化。反編譯源碼發(fā)現(xiàn)class里面的內(nèi)容是
public static final int A = 8;
也就是說編譯器很智能的、在編譯的時候自己就能算出4+4是8,是一個固定的數(shù)字。沒有什么未知的因素在里面。
將代碼稍微改一下
public static final int A = 4 + new Random().nextInt(10);
這個時候靜態(tài)塊就執(zhí)行了,證明類初始化了。在靜態(tài)final變量在編譯時不定的情況下。如果客戶程序這個時候訪問了該類的靜態(tài)變量,那就會對類進行初始化,所以盡量靜態(tài)final變量盡量沒什么可變因素在里面1,否則性能會有所下降。
1. ClassLoader的剖析
ClassLoader的loadClass方法加載一個類不屬于主動調(diào)用,不會導(dǎo)致類的初始化。如下代碼塊
Java代碼
ClassLoader classLoader = ClassLoader.getSystemClassLoader(); Class clazz = classLoader.loadClass("test01.ClassDemo");
并不會讓類加載器初始化test01.ClassDemo,因為這不屬于主動調(diào)用此類。
ClassLoader的關(guān)系:
根加載器——》擴展類加載器——》應(yīng)用類加載器——》用戶自定義類加載器
加載類的過程是首先從根加載器開始加載、根加載器加載不了的,由擴展類加載器加載,再加載不了的有應(yīng)用加載器加載,應(yīng)用加載器如果還加載不了就由自定義的加載器(一定繼承自java.lang. ClassLoader)加載、如果自定義的加載器還加載不了。而且下面已經(jīng)沒有再特殊的類加載器了,就會拋出ClassNotFoundException,表面上異常是類找不到,實際上是class加載失敗,更不能創(chuàng)建該類的Class對象。
若一個類能在某一層類加載器成功加載,那么這一層的加載器稱為定義類加載器。那么在這層類生成的Class引用返回下一層加載器叫做初始類加載器。因為加載成功后返回一個Class引用給它的服務(wù)對象——也就是調(diào)用它的類加載器。考慮到安全,父委托加載機制。
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的方法,也就是通過JNI調(diào)用底層C++的代碼。
當一個類被加載、連接、初始化后,它的生命周期就開始了,當代表該類的Class對象不再被引用、即已經(jīng)不可觸及的時候,Class對象的生命周期結(jié)束。那么該類的方法區(qū)內(nèi)的數(shù)據(jù)也會被卸載,從而結(jié)束該類的生命周期。一個類的生命周期取決于它Class對象的生命周期。由Java虛擬機自帶的默認加載器(根加載器、擴展加載器、系統(tǒng)加載器)所加載的類在JVM生命周期中始終不被卸載。所以這些類的Class對象(我稱其為實例的模板對象)始終能被觸及!而由用戶自定義的類加載器所加載的類會被卸載掉!
看完上述內(nèi)容是否對您有幫助呢?如果還想對相關(guān)知識有進一步的了解或閱讀更多相關(guān)文章,請關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝您對創(chuàng)新互聯(lián)的支持。
網(wǎng)站欄目:如何深入理解Java虛擬機JVM類加載初始化
本文地址:http://www.rwnh.cn/article46/jdcpeg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供微信小程序、全網(wǎng)營銷推廣、網(wǎng)站排名、品牌網(wǎng)站制作、網(wǎng)站改版、電子商務(wù)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)