java類載入相關

NO IMAGE

類載入機制大家應該已經非常熟悉了,採取雙親委派機制,當載入一個類時,首先將載入任務委託給父類載入器,依次遞迴,如果父類載入器可以完成載入任務,就成功返回;如果父類無法載入,才由自己載入。

雙親委派機制的作用:防止記憶體中出現多份相同的位元組碼。

其他規則
1.隱式載入:在當前類中所有new的物件,如果沒有被載入,則使用當前類的類載入器載入 如果類A中引用了類B,Java虛擬機器將使用載入類A的類載入器去載入類B
2.不同類載入器載入的類是不同的,通過類載入器 類全路徑來唯一標識一個類

JVM預定義的三種類載入器
1.Bootstrap ClassLoader:啟動類載入器,它負責將JAVA_HOME/lib下面的類庫載入到記憶體中,如rt.jar;啟動類載入器是由C 寫的二進位制程式碼,不是java類,在JVM啟動的時候Bootstrap就已經啟動。
2.Extension ClassLoader:標準擴充套件類載入器,它負責載入JAVA_HOME/lib/ext或由系統變數java.ext.dir指定位置中的類庫載入到記憶體中。
3.APP ClassLoader:系統類載入器(System ClassLoader),它負責將類路徑CLASSPATH中的類庫載入到記憶體。

載入順序圖如下:

圖片描述

圖中的BootStrapClassLoader、ExtClassLoader、APPClassLoader不是真正的繼承關係,只是邏輯上的上下級類載入器;

實際上的類關係如下圖:

圖片描述

可以看到ExtClassLoader和APPCLassLoader都繼承自URLClassLoader,也就證實了二者並非真正的繼承關係;

通過上圖可以看到最頂層的類是抽象類ClassLoader:是所有類載入器的基類(除了啟動類載入器),定義了類載入最核心的操作;
SecureClassLoader:新增了關聯類原始碼、關聯絡統許可權支援
URLClassLoader:支援從jar檔案和資料夾中獲取class
ExtClassLoader:擴充套件類載入器Extension ClassLoader
APPClassLoader:系統類載入器,也稱為System ClassLoader

ClassLoader:

父子類載入器是通過ClassLoader一個parent屬性來標識,APPClassLoader的父載入器是ExtClassLoader,ExtClassLoader的父載入器是null。

ClassLoader提供了兩個構造器,一個是沒有引數的,一個是有引數的;如下圖:

圖片描述

圖片描述

沒有引數的構造器預設將系統類載入器作為parent載入器;
有引數的構造器將引數指定的載入器作為父類載入器;

Launcher:

ExtClassLoader和AppClassLoader都是Launcher的子類,在ClassLoader初始化或者直接通過ClassLoader的getSystemClassLoader()獲取的時候會呼叫initSystemClassLoader(),從而呼叫sun.misc.Launcher.getLauncher(),將系統類載入器賦值給ClassLoader的scl變數;

我們看下Launcher類初始化的時候都做了什麼工作,如圖:

圖片描述

主要是三部工作:
1.建立ExtClassLoader
2.建立AppClassLoader
3.將執行緒系統類載入器設定為執行緒上下文類載入器,什麼是上線文類載入器?

執行緒上下文類載入器
java提供了為很多服務商提供了介面,簡稱SPI(Service Provider Interface),具體的實現由各廠商提供,例如mysql驅動,oracle驅動等。例如:mysql驅動載入介面類在rt.jar中,由啟動類載入器載入,具體實現類在mysql驅動包中,驅動包一般放到我們自己的程式路徑lib下,應該由系統類載入器載入;但是在使用如下程式碼進行資料庫連線使用操作的時候,就會出現在rt.jar中要載入驅動包裡程式碼的情況(類載入器是啟動類載入器),由隱式載入規則可知,驅動包也要使用啟動類載入器載入,由類載入機制可知,是無法通過啟動類載入器來載入的;那這種情況怎麼辦呢,就要通過執行緒上下文類載入器來解決。

上面描述的情況如下:使用jdbc進行資料庫操作如下

1.Class.forName("com.mysql.jdbc.Driver");// 載入mysql驅動
2.Connection conn = DriverManager.getConnection(url);//建立連線
3.Statement stmt = conn.createStatement();//得到statement物件
4.運算元據庫  關閉連線。。

第一步在例項化Driver時,會呼叫DriverManager的registerDriver()方法收集divers,將驅動類註冊到DriverManager容器中,DriverManage的drivers容器:

圖片描述

註冊的程式碼:

圖片描述

Class.forName(“com.mysql.jdbc.Driver”)相當於:
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Class driversClass = loader.loadClass(“com.mysql.jdbc.Driver”);
driversClass.newInstance();
由此可見com.mysql.jdbc.Driver是由我們自己應用類載入器AppClassLoader進行載入;

第二步通過DriverManager.getConnection(url),會迴圈獲取drivers中的driver,呼叫具體driver實現裡的cnnect()方法,進行連線

圖片描述

caller.getClassLoader()是啟動類載入器為null,因此callerCL為系統類載入器

圖片描述

getConnection通過isDriverAllowed方法校驗類是否有許可權被載入

圖片描述

通過AppClassLoader來載入Driver看是否和已註冊的Driver是同一個類,如果是則呼叫driver的connect方法

在java6以後,引入了service provider概念,在/META-INF/services/java.sql.Driver檔案中配置需要載入的驅動類,
在DriverManager初始化的時候會呼叫loadInitialDrivers方法,

圖片描述

會使用AppClassLoader進行載入,所以在自己程式中可以不用Class.forName顯示呼叫。

上面包類結構如下圖:

圖片描述

tomcat類載入
我們執行tomcat的多個例項,不想安裝tomcat軟體副本,我們可以配置多個工作目錄,每個執行例項獨佔一個工作目錄,但是共享一個安裝目錄。
變數解釋:
CATALINA_HOME:tomcat安裝目錄,多個工作目錄可共享安裝目錄
CATALINA_BASE:tomcat工作目錄,tomcat每個執行例項需要使用自己的conf、logs、temp、webapps、work、shared目錄,CATALINA_BASE就是指向這個目錄

如下圖:兩個應用公用CATALINA_HOME,CATALINA_BASE指向各自工作目錄

圖片描述
圖片描述

首先看下tomcat在啟動時類初始化類載入器過程

圖片描述

首先是建立commonClassLoader,commonClassLoader載入的是配置檔案catalina.properties中配置的

圖片描述

common.loader=${catalina.home}/lib,${catalina.home}/lib/*.jar
server.loader=
shared.loader=
例如在我們的伺服器上路徑是:
/opt/soft/tomcat/lib、/opt/soft/tomcat/lib/*.jar

因為server.loader和shared.loader未配置具體載入目錄資訊,catalinaLoader和sharedLoader預設為commonLoader(在tomcat5以後catalinaLoader和sharedLoader預設不啟用)

圖片描述

tomcat一共定義了兩種類載入器
StandardClassLoader:例項化commonloader、catalinaLoader、sharedLoader,不提供熱部署功能,遵循雙親委派機制
WebappClassLoader:和context級容器相關聯,載入web程式,支援其載入路徑下資源改變後重新載入,不遵循雙親委派機制。

其類繼承關係如下:

圖片描述

除此之外還有兩個類:WebappLoader和VirtualWebappLoader,該兩個類不是類載入器,只是對WebappClassLoader做了封裝,對熱部署、生命週期控制等功能做了一些控制;
VirtualWebappLoader是WebappLoader的子類,主要是和conf/context.xml這個檔案相關聯,主要功能是載入context.xml配置檔案中設定的一些java類庫,由於WebappClassLoader只能載入WEB-INF/class和WEB-INF/lib下的類庫。而想擴充套件一下載入路徑又不想新增到WEB-INF/lib中的時候,可以配置在context.xml檔案中。

自己實現類記載器只要實現findclass即可,這裡為了實現特殊目的而override了loadClass();WebappClassLoader重寫了loadClass方法,先自己載入,如果載入不了再進行其他操作。

所以在Tomcat 6中預設情況下,不是完全按照先Tomcat的lib再Web應用的lib這種順序去載入類。
Jar包的載入順序是:
1)JRE中的Java基礎包
2)Web應用WEB-INF/lib下的包
3)Tomcat/lib下的包

如果想要在Web應用間共享一些Jar包,則不僅需要將公共包放在Tomcat的lib下,還要刪掉Web應用lib中的包,否則Tomcat啟動時還是會優先載入Web應用lib下的包的。

如果想要自己指定一個Tomcatlib和Web應用lib之外的ClassPath,除了修改Tomcat啟動指令碼外,可以為不同Web應用的Context指定一個VirtualWebappLoader,但原始碼註釋中寫到不推薦在生產環境中使用。