通俗易懂理解JAVA虛擬機(一)——類加載詳解

通俗易懂理解JAVA虛擬機(一)

一:類加載機制的深刻剖析*
一、類加載過程
多個java文件通過編譯打包生成可運行jar包,最終由java命令運行某個主類的main函數啓動程序,這裏首先須要經過類加載器把主類加載到JVM。主類在運行過程當中若是使用到其它類,會逐步加載這些類。注意,jar包裏的類不是一次性所有加載的,是使用到時才加載。
類加載到使用整個過程有以下幾步:加載 >> 驗證 >> 準備 >> 解析 >> 初始化 >> 使用 >> 卸載
加載:在硬盤上查找並經過IO讀入字節碼文件,使用到類時纔會加載,例如調用類的main()方法,new對象等等
驗證:校驗字節碼文件的正確性
準備:給類的靜態變量分配內存,並賦予默認值
解析:將符號引用替換爲直接引用,該階段會把一些靜態方法(符號引用,好比main()方法)替換爲指向數據所存內存的指針或句柄等(直接引用),這是所謂的靜態連接過程(類加載期間完成),動態連接是在程序運行期間完成的將符號引用替換爲直接引用,下節課會講到動態連接
初始化:對類的靜態變量初始化爲指定的值,執行靜態代碼塊
在這裏插入圖片描述
二、類加載器和雙親委派機制
上面的類加載過程主要是經過類加載器來實現的,Java裏有以下幾種類加載器
啓動類加載器:負責加載支撐JVM運行的位於JRE的lib目錄下的核心類庫,好比rt.jar、charsets.jar等
擴展類加載器:負責加載支撐JVM運行的位於JRE的lib目錄下的ext擴展目錄中的JAR類包
應用程序類加載器:負責加載ClassPath路徑下的類包,主要就是加載你本身寫的那些類
自定義加載器:負責加載用戶自定義路徑下的類包
看一個類加載器示例:
在這裏插入圖片描述
自定義一個類加載器示例:自定義類加載器只須要繼承 java.lang.ClassLoader 類,該類有兩個核心方法,一個是loadClass(String, boolean),實現了雙親委派機制,大致邏輯java

  1. 首先,檢查一下指定名稱的類是否已經加載過,若是加載過了,就不須要再加載,直接返回。web

  2. 若是此類沒有加載過,那麼,再判斷一下是否有父加載器;若是有父加載器,則由父加載器加載(即調用parent.loadClass(name, false);).或者是調用bootstrap類加載器來加載。bootstrap

  3. 若是父加載器及bootstrap類加載器都沒有找到指定的類,那麼調用當前類加載器的findClass方法來完成類加載。tomcat

    還有一個方法是findClass,默認實現是拋出異常,因此咱們自定義類加載器主要是重寫findClass方法。
    在這裏插入圖片描述
    在這裏插入圖片描述
    在這裏插入圖片描述安全

在這裏插入圖片描述
在這裏插入圖片描述
這裏類加載其實就有一個雙親委派機制,加載某個類時會先委託父加載器尋找目標類,找不到再委託上層父加載器加載,若是全部父加載器在本身的加載類路徑下都找不到目標類,則在本身的類加載路徑中查找並載入目標類。
好比咱們的Math類,最早會找應用程序類加載器加載,應用程序類加載器會先委託擴展類加載器加載,擴展類加載器再委託啓動類加載器,頂層啓動類加載器在本身的類加載路徑裏找了半天沒找到Math類,則向下退回加載Math類的請求,擴展類加載器收到回覆就本身加載,在本身的類加載路徑裏找了半天也沒找到Math類,又向下退回Math類的加載請求給應用程序類加載器,應用程序類加載器因而在本身的類加載路徑裏找Math類,結果找到了就本身加載了。
雙親委派機制說簡單點就是,先找父親加載,不行再由兒子本身加載服務器

爲何要設計雙親委派機制?
1.沙箱安全機制:
本身寫的java.lang.String.class類不會被加載,這樣即可以防止核心API庫被隨意篡改
2.避免類的重複加載:當父親已經加載了該類時,就沒有必要子ClassLoader再加載一次,保證被加載類的惟一性app

打破雙親委派
以Tomcat類加載爲例,Tomcat 若是使用默認的雙親委派類加載機制行不行?
咱們思考一下:Tomcat是個web容器, 那麼它要解決什麼問題:webapp

  1. 一個web容器可能須要部署兩個應用程序,不一樣的應用程序可能會依賴同一個第三方類庫的不一樣版本,不能要求同一個類庫在同一個服務器只有一份,所以要保證每一個應用程序的類庫都是獨立的,保證相互隔離。
  2. 部署在同一個web容器中相同的類庫相同的版本能夠共享。不然,若是服務器有10個應用程序,那麼要有10份相同的類庫加載進虛擬機
  3. web容器也有本身依賴的類庫,不能與應用程序的類庫混淆。基於安全考慮,應該讓容器的類庫和程序的類庫隔離開來。
  4. web容器要支持jsp的修改,咱們知道,jsp 文件最終也是要編譯成class文件才能在虛擬機中運行,但程序運行後修改jsp已是司空見慣的事情, web容器須要支持 jsp 修改後不用重啓。
    再看看咱們的問題:Tomcat 若是使用默認的雙親委派類加載機制行不行?答案是不行的。爲何?
    第一個問題,若是使用默認的類加載器機制,那麼是沒法加載兩個相同類庫的不一樣版本的,默認的類加器是無論你是什麼版本的,只在意你的全限定類名,而且只有一份。第二個問題,默認的類加載器是可以實現的,由於他的職責就是保證惟一性。第三個問題和第一個問題同樣。咱們再看第四個問題,咱們想咱們要怎麼實現jsp文件的熱加載,jsp 文件其實也就是class文件,那麼若是修改了,但類名仍是同樣,類加載器會直接取方法區中已經存在的,修改後的jsp是不會從新加載的。那麼怎麼辦呢?咱們能夠直接卸載掉這jsp文件的類加載器,因此你應該想到了,每一個jsp文件對應一個惟一的類加載器,當一個jsp文件修改了,就直接卸載這個jsp類加載器。從新建立類加載器,從新加載jsp文件。
    Tomcat自定義加載器詳解

在這裏插入圖片描述
tomcat的幾個主要類加載器:
commonLoader:Tomcat最基本的類加載器,加載路徑中的class能夠被Tomcat容器自己以及各個Webapp訪問;
catalinaLoader:Tomcat容器私有的類加載器,加載路徑中的class對於Webapp不可見;
sharedLoader:各個Webapp共享的類加載器,加載路徑中的class對於全部Webapp可見,可是對於Tomcat容器不可見;WebappClassLoader:各個Webapp私有的類加載器,加載路徑中的class只對當前Webapp可見;jsp

從圖中的委派關係中能夠看出:CommonClassLoader能加載的類均可以被CatalinaClassLoader和SharedClassLoader使用,從而實現了公有類庫的共用,而CatalinaClassLoader和SharedClassLoader本身能加載的類則與對方相互隔離。
WebAppClassLoader可使用SharedClassLoader加載到的類,但各個WebAppClassLoader實例之間相互隔離。
而JasperLoader的加載範圍僅僅是這個JSP文件所編譯出來的那一個.Class文件,它出現的目的就是爲了被丟棄:當Web容器檢測到JSP文件被修改時,會替換掉目前的JasperLoader的實例,並經過再創建一個新的Jsp類加載器來實現JSP文件的熱加載功能。
tomcat 這種類加載機制違背了java 推薦的雙親委派模型了嗎?答案是:違背了。
咱們前面說過,雙親委派機制要求除了頂層的啓動類加載器以外,其他的類加載器都應當由本身的父類加載器加載。很顯然,tomcat 不是這樣實現,tomcat 爲了實現隔離性,沒有遵照這個約定,每一個webappClassLoader加載本身的目錄下的class文件,不會傳遞給父類加載器,打破了雙親委派機制。svg