類加載機制
- JVM把class文件加載到內存,並對數據進行校驗、解析和初始化,最終造成 JVM能夠直接使用的Java類型的過程。
- 將Java類的二進制代碼合併到JVM的運行狀態之中的過程
- 驗證: 確保加載的類信息符合JVM規範,沒有安全方面的問題。
- 準備: 正式爲類變量(static變量)分配內存並設置類變量初始值的階段,這些內存都將在方法區中進行分配
- 解析: 虛擬機常量池內的符號引用替換爲直接引用的過程
- 初始化:
- 初始化階段是執行類構造器
<clinit>()
方法的過程。類構造器<clinit>()
方法是由編譯器自動收集 類中的全部類變量的賦值動做和靜態語句塊(static塊)中的語句合併產生的。 - 當初始化一個類的時候,若是發現其父類尚未進行過初始化、則須要先出發其父類的初始化
- 虛擬機會保證一個類的
<clinit>()
方法在多線程環境中被正確加鎖和同步。
- 初始化階段是執行類構造器
類的主動引用(必定會發生類的初始化)
- new一個類的對象
- 調用類的靜態成員(除了final常量)和靜態方法
- 使用java.lang.reflect包的方法對類進行反射調用
- 當虛擬機啓動,java Hello,則必定會初始化Hello類。說白了就是先啓動main方法所在的類
- 當初始化一個類,若是其父類沒有被初始化,則先會初始化他的父類
類的被動引用(不會發生類的初始化)
- 當訪問一個靜態域時,只有真正聲明這個域的類纔會被初始化
- 經過子類引用父類的靜態變量,不會致使子類初始化 – 經過數組定義類引用,不會觸發此類的初始化
- 引用常量不會觸發此類的初始化(常量在編譯階段就存入調用類的常量池中了)
類加載器的做用
將class文件字節碼內容加載到內存中,並將這些靜態數據轉換成方法 區中的運行時數據結構,在堆中生成一個表明這個類的java.lang.Class
對象,做爲方法區類數據的訪問入口。java
類緩存
標準的Java SE類加載器能夠按要求查找類,但一旦某個類被加載到類加載 器中,它將維持加載(緩存)一段時間。不過,JVM垃圾收集器能夠回收 這些Class對象。mysql
ClassLoder
做用
java.lang.ClassLoader
類的基本職責就是根據一個指定的類的名稱, 找到或者生成其對應的字節代碼,而後從這些字節代碼中定義出一個 Java 類,即java.lang.Class
類的一個實例。- 除此以外,ClassLoader還負責加載 Java 應用所需的資源,如圖像文 件和配置文件等。
- 相關方法
getParent()
返回該類加載器的父類加載器。loadClass(String name)
加載名稱爲 name的類,返回的結果是java.lang.Class
類的實例。findClass(String name)
查找名稱爲 name的類,返回的結果是java.lang.Class
類的實例。findLoadedClass(String name)
查找名稱爲 name的已經被加載過的類,返回的結果是java.lang.Class
類的實例defineClass(String name, byte[] b, int off, int len)
把字節數組 b中的內容轉換成 Java 類,返回的結果是java.lang.Class
類的實例.這個方法被聲明爲 final的。resolveClass(Class<?> c)
連接指定的 Java 類。
對於以上給出的方法,表示類名稱的 name參數的值是類的二進制名稱。須要注意的是內部類的表示,如com.example.Sample$1
和com.example.Sample$Inner
等表示方式。web
類加載器的層次結構
- 引導類加載器(bootstrap class loader)
- 它用來加載 Java 的核心庫
(JAVA_HOME/jre/lib/rt.jar,
或sun.boot.class.path
路徑下的 內容),是用原生代碼來實現的,並不繼承自 java.lang.ClassLoader。 - 加載擴展類和應用程序類加載器。並指定他們的父類加載器。
- 它用來加載 Java 的核心庫
- 擴展類加載器(extensions class loader)
- 用來加載 Java 的擴展庫
(JAVA_HOME/jre/ext/*.jar,
或java.ext.dirs
路徑下的內容) 。 Java 虛擬機的實現會提供一個擴展庫目錄。該類加載器在此目錄裏面查找並加載 Java 類。 - 由
sun.misc.Launcher$ExtClassLoader
實現
- 用來加載 Java 的擴展庫
- 應用程序類加載器(application class loader)
- 它根據 Java 應用的類路徑(classpath, java.class.path 路徑下的內容)來加載 Java 類。 通常來講,Java 應用的類都是由它來完成加載的。
- 由
sun.misc.Launcher$AppClassLoader
實現
- 自定義類加載器
- 開發人員能夠經過繼承
java.lang.ClassLoader
類的方式 - 實現本身的類加載器,以知足一些特殊的需求。
- 開發人員能夠經過繼承
類加載器的代理模式
代理模式
- 交給其餘加載器來加載指定的類
雙親委託機制
- 就是某個特定的類加載器在接到加載類的請求時,首先將加載任務委 託給父類加載器,依次追溯,直到最高的爺爺輩的,若是父類加載器 能夠完成類加載任務,就成功返回;只有父類加載器沒法完成此加載 任務時,才本身去加載。
- 雙親委託機制是爲了保證 Java 核心庫的類型安全。
- 這種機制就保證不會出現用戶本身能定義
java.lang.Object
類的狀況。 - 類加載器除了用於加載類,也是安全的最基本的屏障。
雙親委託機制是代理模式的一種
- 並非全部的類加載器都採用雙親委託機制。
- tomcat服務器類加載器也使用代理模式,所不一樣的是它是首先嚐試去加載某個類,若是找不到再代理給父類加載器。 這與通常類加載器的順序是相反的
自定義類加載器的流程
- 一、首先檢查請求的類型是否已經被這個類裝載器裝載到命名空間中了,若是已經裝載,直接返回;不然轉入步驟2
- 二、委派類加載請求給父類加載器(更準確的說應該是雙親類加載器,真個虛擬機中各類類加載器最終會呈現樹狀結構),若是父類加載器可以完成,則返回父類加載器加載的Class實例;不然轉入步驟3
- 三、調用本類加載器的
findClass(…)
方法,試圖獲取對應的字節碼,若是獲取的到,則調用defineClass(…)
導入類型到方法區;若是獲取不到對應的字節碼或者其餘緣由失敗,返回異常給loadClass(…)
,loadClass(…)
轉拋異常,終止加載過程(注意:這裏的 異常種類不止一種)。
注意:被兩個類加載器加載的同一個類,JVM不認爲是相同的類。sql
線程上下文類加載器
-
雙親委託機制以及默認類加載器的問題bootstrap
- 通常狀況下, 保證同一個類中所關聯的其餘類都是由當前類的類加載器所加載的.。 好比,ClassA自己在Ext下找到,那麼他裏面new出來的一些類也就只能用Ext去查找了(不會低一個級別),因此有些明明App能夠找到的,卻找不到了。
- JDBC API,他有實現的driven部分
(mysql/sql server)
,咱們的JDBC API都是由Boot或者Ext來載入的,可是 JDBC driver倒是由Ext或者App來載入,那麼就有可能找不到driver了。在Java領域中,其實只要分紅這種Api+SPI( Service Provide Interface,特定廠商提供)
的,都會遇到此問題。 - 常見的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。這些 SPI 的接口由 Java 核心庫來提供,如 JAXP 的 SPI 接口定 義包含在
javax.xml.parsers
包中。SPI 的接口是 Java 核心庫的一部分,是由引導類加載器來加載的;SPI 實現的 Java 類通常是由系統類加載器來加載的。引導類加載器是沒法找到 SPI 的實現類的,由於它只加載 Java 的核心庫
-
一般當你須要動態加載資源的時候 , 你至少有三個 ClassLoader 能夠選擇 :數組
- 1.系統類加載器或叫做應用類加載器
(system classloader or application classloader)
- 2.當前類加載器
- 3.當前線程類加載器
- 1.系統類加載器或叫做應用類加載器
-
當前線程類加載器是爲了拋棄雙親委派加載鏈模式。緩存
- 每一個線程都有一個關聯的上下文類加載器。若是你使用
new Thread()
方式生成新的線程,新線程將繼承其父線程的上下文類加載器。若是程序對線程上下文類加載器沒有任何改動的話,程序中全部的線程將都使用系統類加載器做爲上下文類加載器。
- 每一個線程都有一個關聯的上下文類加載器。若是你使用
-
Thread.currentThread().getContextClassLoader()
tomcat
TOMCAT服務器的類加載機制
- TOMCAT不能使用系統默認的類加載器。
- 若是TOMCAT跑你的WEB項目使用系統的類加載器那是至關危險的,你能夠直接是無忌憚是操做系統的各個目錄了。
- 對於運行在 Java E容器中的 Web 應用來講,類加載器的實現方式與一 般的 Java 應用有所不一樣。
- 每一個 Web 應用都有一個對應的類加載器實例。該類加載器也使用代理模 式(不一樣於前面說的雙親委託機制),所不一樣的是它是首先嚐試去加載某個類,若是找不到再代理給父類加載器。這與通常類加載器的順序是相反的 。但也是爲了保證安全,這樣核心庫就不在查詢範圍以內。
OSGI原理介紹
- OSGi™是 Java 上的動態模塊系統。它爲開發人員提供了面向服務和基於組件的運 行環境,並提供標準的方式用來管理軟件的生命週期。
- OSGi 已經被實現和部署在不少產品上,在開源社區也獲得了普遍的支持。Eclipse 就是基於 OSGi 技術來構建的。
- 原理:
- OSGi 中的每一個模塊(bundle)都包含 Java 包和類。模塊能夠聲明它所依賴的須要導入 (import)的其它模塊的 Java 包和類(經過 Import-Package),也能夠聲明導出( export)本身的包和類,供其它模塊使用(經過 Export-Package)。也就是說須要可以隱藏和共享一個模塊中的某些 Java 包和類。這是經過 OSGi 特有的類加載器機制來實現的。OSGi 中的每一個模塊都有對應的一個類加載器。它負責加載模塊本身包含的 Java 包和類。當它須要加載 Java 核心庫的類時(以 java開頭的包和類),它會代理給父類加載器(一般是啓動類加載器)來完成。當它須要加載所導入的 Java 類時,它會 代理給導出此 Java 類的模塊來完成加載。模塊也能夠顯式的聲明某些 Java 包和類,必 須由父類加載器來加載。只須要設置系統屬性
org.osgi.framework.bootdelegation
的值便可。
- OSGi 中的每一個模塊(bundle)都包含 Java 包和類。模塊能夠聲明它所依賴的須要導入 (import)的其它模塊的 Java 包和類(經過 Import-Package),也能夠聲明導出( export)本身的包和類,供其它模塊使用(經過 Export-Package)。也就是說須要可以隱藏和共享一個模塊中的某些 Java 包和類。這是經過 OSGi 特有的類加載器機制來實現的。OSGi 中的每一個模塊都有對應的一個類加載器。它負責加載模塊本身包含的 Java 包和類。當它須要加載 Java 核心庫的類時(以 java開頭的包和類),它會代理給父類加載器(一般是啓動類加載器)來完成。當它須要加載所導入的 Java 類時,它會 代理給導出此 Java 類的模塊來完成加載。模塊也能夠顯式的聲明某些 Java 包和類,必 須由父類加載器來加載。只須要設置系統屬性
本文同步分享在 博客「cwl_java」(CSDN)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。安全