delphi7 获取dll的类_跟我学Java内存管理----JMM精华终章(类加载器)
1 類加載器
1.1 類的加載過程
當程序要使用某個類時,如果該類還未被加載到內存中,則系統會通過加載,連接,初始化三步來實現對這個類進行初始化。
(1)加載
就是指將class文件讀入內存,并為之創建一個Class對象。
任何類被使用時系統都會建立一個Class對象。
(2)連接
驗證 是否有正確的內部結構,并和其他類協調一致
準備 負責為類的靜態成員分配內存,并設置默認初始化值
解析 將常量池內的符號引用(邏輯地址)替換成直接引用(物理地址)
(3)初始化 就是我們以前講過的初始化步驟
類初始化時機
l 創建類的實例
l 訪問類的靜態變量,或者為靜態變量賦值
l 調用類的靜態方法
l 使用反射方式來強制創建某個類或接口對應的java.lang.Class對象
l 初始化某個類的子類
l 直接使用java.exe命令來運行某個主類
在類加載時,靜態變量和靜態代碼塊的加載順序(執行順序),由編寫先后決定。
在實例化時,成員變量和成員代碼塊的執行順序,由編寫先后決定。
1.2 什么是類加載器
類加載器就是用來加載類的東西,類加載器也是一個類:ClassLoader(把.class文件加載到JVM的方法區中,變成一個Class對象)。一些在java代碼中動態生成的類,而這些類的數據就是在運行期時由類加載器去加載的,比如動態代理。
類加載器也可以被加載到內存,是通過其他類加載器完成的!Java提供了三種類加載器(比喻,它們都是片警!),分別是:
l bootstrap classloader:引導類加載器,加載rt.jar中的類(類庫);
l sun.misc.Launcher$ExtClassLoader:擴展類加載器,加載lib/ext目錄下的類;
l sun.misc.Launcher$AppClassLoader:應用類加載器,加載CLASSPATH下的類,即我們寫的類和第三方的jar包,以及第三方提供的類。(會先加載CLASSPATH下的類,然后加載lib目錄下的jar包;)
通常情況下,Java中所有類都是通過這三個類加載器加載的。
類加載器之間存在上下級關系,應用類加載器的上級是擴展類加載器,而擴展類加載器的上級是引導類加載器,引導類加載器(該加載器本身無需加載,存在JVM中)沒上層,它是BOSS。
當執行 java ***.class 的時候, java.exe 會幫助我們找到 JRE ,接著找到位于 JRE 內部的 jvm.dll ,這才是真正的 Java 虛擬機器 , 最后加載動態庫,激活 Java 虛擬機器。虛擬機器激活以后,會先做一些初始化的動作,比如說讀取系統參數等。一旦初始化動作完成之后,就會產生第一個類加載器―― Bootstrap Loader , Bootstrap Loader 是由 C++ 所撰寫而成,這個 Bootstrap Loader 所做的初始工作中,除了一些基本的初始化動作之外,最重要的就是加載 Launcher.java 之中的 ExtClassLoader ,并設定其 Parent 為 null ,代表其父加載器為 BootstrapLoader 。然后 Bootstrap Loader 再要求加載 Launcher.java 之中的 AppClassLoader ,并設定其 Parent 為之前產生的 ExtClassLoader 實體。這兩個加載器都是以靜態類的形式存在的。這里要請大家注意的是, Launcher$ExtClassLoader.class 與 Launcher$AppClassLoader.class 都是由 BootstrapLoader 所加載,所以 Parent 和由哪個類加載器加載沒有關系。
1.3 JVM眼中的相同的類
在JVM中,不可能存在一個類被加載兩次的事情!一個類如果已經被加載了,當再次試圖加載這個類時,類加載器會先去查找這個類是否已經被加載過了,如果已經被加載過了,就不會再去加載了。
但是,如果一個類使用不同的類加載器去加載是可以出現多次加載的情況的!也就是說,在JVM眼中,相同的類需要有相同的class文件,以及相同的類加載器。當一個class文件,被不同的類加載器加載了,JVM會認識這是兩個不同的類,這會在JVM中出現兩個相同的Class對象!甚至會出現類型轉換異常!
1.4類加載器的委托機制
當系統類加載器去加載一個類時,它首先會讓上級去加載,即讓擴展類加載器去加載類,擴展類加載器也會讓它的上級引導類加載器去加載類。如果上級沒有加載成功,那么再由自己去加載!
例如我們自己寫的Person類,一定是存放到CLASSPATH中,那么一定是由系統類加載器來加載。當系統類加載器來加載類時,它首先把加載的任務交給擴展類加載去,如果擴展類加載器加載成功了,那么系統類加載器就不會再去加載。這就是代理模式了!
相同的道理,擴展類加載器也會把加載類的任務交給它的“上級”,即引導類加載器,引導類加載器加載成功,那么擴展類加載器也就不會再去加載了。引導類加載器是用C語言寫的,是JVM的一部分,它是最上層的類加載器了,所以它就沒有“上級了”。它只負責去加載“內部人”,即JDK中的類,但我們知道Person類不是我們自己寫的類,所以它加載失敗。
當擴展類加載器發現“上級”不能加載類,它就開始加載工作了,它加載的是libext目錄下的jar文件,當然,它也會加載失敗,所以最終還是由系統類加載器在CLASSPATH中去加載Person,最終由系統類加載器加載到了Person類。
代理模式保證了JDK中的類一定是由引導類加載加載的!這就不會出現多個版本的類,這也是代理模式的好處。例如自定義的String類永遠不會被加載。
1.5 自定義類加載器
我們也可以通過繼承ClassLoader類來完成自定義類加載器,自類加載器的目的一般是為了加載網絡上的類,因為這會讓class在網絡中傳輸,為了安全,那么class一定是需要加密的,所以需要自定義的類加載器來加載(自定義的類加載器需要做解密工作)。
ClassLoader加載類都是通過loadClass()方法來完成的,loadClass()方法的工作流程如下:
l 調用findLoadedClass()方法查看該類是否已經被加載過了(在JVM的方法區中查看已加載過的類!),如果該沒有加載過,那么這個方法返回null;
l 判斷findLoadedClass()方法返回的是否為null,如果不是null那么直接返回,這可以避免同一個類被加載兩次;
l 如果findLoadedClass()返回的是null,那么就啟動代理模式(委托機制),即調用上級的loadClass()方法,獲取上級的方法是getParent(),當然上級可能還有上級,這個動作就一直向上走;
l 如果getParent().loadClass()返回的不是null,這說明上級加載成功了,那么就加載結果;
l 如果上級返回的是null,這說明需要自己出手了,這時loadClass()方法會調用本類的findClass()方法來加載類;
l 這說明我們只需要重寫ClassLoader的findClass()方法,這就可以了!如果重寫了loadClass()方法覆蓋了代理模式!
OK,通過上面的分析,我們知道要自定義一個類加載器,只需要繼承ClassLoader類,然后重寫它的findClass()方法即可。那么在findClass()方法中我們要完成哪些工作呢?
l 找到class文件,把它加載到一個byte[]中;
l 調用defineClass()方法,把byte[]傳遞給這個方法即可。
1.6Tomcat的類加載器
Tomcat提供了兩種類加載器!
* 服務器類加載器:它負責加載這個下面的類!(${CATALINA_HOME}lib)
* 應用類加載器:它負責加載這兩個路徑下的類!(${CONTEXT_HOME}WEB-INFlib、${CONTEXT_HOME}WEB-INFclasses)。
注意:Tomcat會為每個應用(假設該Tomcat下部署了多個應用)提供一個應用類加載器,負責加載每個該應用下WEB-INFlib和WEB-INFclasses目錄下的類。
對于運行在 Java EE容器中的 Web 應用來說,類加載器的實現方式與一般的 Java 應用有所不同。不同的 Web 容器的實現方式也會有所不同。以 Apache Tomcat 來說,每個 Web 應用都有一個對應的類加載器實例。該類加載器也使用代理模式,所不同的是它是首先嘗試去加載某個類,如果找不到再代理給父類加載器去加載。這與一般類加載器的順序是相反的。這是 Java Servlet 規范中的推薦做法,其目的是使得 Web 應用自己的類的優先級高于 Web 容器提供的類。
這種代理模式的一個例外是:Java 核心庫的類是不在查找范圍之內的。這也是為了保證 Java 核心庫的類型安全。(tomcat的webApp類加載器,在加載用戶自定義類時,先嘗試讓App類加載器判斷下是不是jdk的核心類,如果是,就由App類加載器加載;否則就自己來加載;如果自己還加載不了,就交給服務器類加載器去加載。)
Tomcat提供的類加載器有這樣一個好處,就是對每個應用來說,可以使該應用下的類優先被加載!(優先于(${CATALINA_HOME}lib目錄下的類)。
相同名稱的類可以并存在 Java 虛擬機中,只需要用不同的類加載器來加載它們即可。不同類加載器加載的類之間是不兼容的,這就相當于在 Java 虛擬機內部創建了一個個相互隔離的 Java 類空間。這種技術在許多框架中都被用到,例如OSGI、Web 容器(Tomcat)。
1.7加載資源文件
我們在開發中,要讀取配置文件時,為什么不使用File的形式讀取?因為應用有可能被打包成jar文件,單純地用File去讀取jar包的文件是不能的,因為File的方式并不會深入到jar包內部的路徑(即不會解壓jar文件),那么就無法讀取到jar內部的配置文件了。所以,如果jar包中的類源代碼用File f=new File(相對路徑)的形式,是不可能定位到文件資源的,會報FileNotFoundException的異常信息。
而如果用ClassLoader來加載資源文件,就能解決這個問題。ClassLoader會使用文件資源定位符的格式 (jar中資源有其專門的URL形式: jar:!/{entry} )。
1.8怎樣獲取ClassLoader呢
Thread.currentThread().getContextClassLoader()和ClassLoader.getSystemClassLoader()如何選擇?
分別在IDEA中運行、以jar的方式運行上面Controller的代碼查看結果。
在IDEA開發工具中運行時直接調用ClassLoader.getSystemClassLoader()調用的是AppClassLoader,在IDEA環境時可以加載到target目錄下的所有文件。而如果在springBoot項目的 jar方式運行時仍然還是用AppClassLoader去加載項目內的靜態資源的話就會找不到的,因為springBoot加載靜態資源文件是用的內置tomcat的classloader去加載的。
總結:
為了避免在項目中加載不到本項目中靜態資源文件的BUG發生,調用靜態資源的classLoader最好用Thread.currentThread().getContextClassLoader()方法來獲取,因為一般同一個項目中java代碼和其靜態資源文件都是同一個classLoader來加載的,以此確保通過此classLoader也能加載到本項目中的資源文件。
遺留知識
1.SPI機制
2.字節碼技術
3.SpringBoot的spring.factories原理
4.Dubbo的SPI擴展原理
預知后事如何,且聽下回分解。
JMM的內容很多很深,已經盡量濃縮了。打字不容易,記得關注——收藏——轉發哦。
總結
以上是生活随笔為你收集整理的delphi7 获取dll的类_跟我学Java内存管理----JMM精华终章(类加载器)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 儿子获奖发明和父亲研究所成果高度相似,还
- 下一篇: python分类预测降低准确率_十分钟掌