日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

深入理解Java ClassLoader及在 JavaAgent 中的应用

發布時間:2023/12/3 java 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深入理解Java ClassLoader及在 JavaAgent 中的应用 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

轉載自? ?深入理解Java ClassLoader及在 JavaAgent 中的應用

背景

眾所周知, Java 或者其他運行在 JVM(java 虛擬機)上面的程序都需要最終便以為字節碼,然后被 JVM加載運行,那么這個加載到虛擬機的過程就是 classloader 類加載器所干的事情.直白一點,就是 通過一個類的全限定類名稱來獲取描述此類的二進制字節流 的過程.

雙親委派模型

說到 Java 的類加載器,必不可少的就是它的雙親委派模型,從 Java 虛擬機的角度來看,只存在兩種不同的類加載器:

  • 啟動類加載器(Bootstrap ClassLoader), 由 C++語言實現,是虛擬機自身的一部分.
  • 其他的類加載器,都是由 Java 實現,在虛擬機的外部,并且全部繼承自java.lang.ClassLoader
  • 在 Java 內部,絕大部分的程序都會使用 Java 內部提供的默認加載器.

    啟動類加載器(Bootstrap ClassLoader)

    負責將$JAVA_HOME/lib或者 -Xbootclasspath 參數指定路徑下面的文件(按照文件名識別,如 rt.jar) 加載到虛擬機內存中.啟動類加載器無法直接被 java 代碼引用,如果需要把加載請求委派給啟動類加載器,直接返回null即可.

    擴展類加載器(Extension ClassLoader)

    負責加載$JAVA_HOME/lib/ext 目錄中的文件,或者java.ext.dirs 系統變量所指定的路徑的類庫.

    應用程序類加載器(Application ClassLoader)

    一般是系統的默認加載器,比如用 main 方法啟動就是用此類加載器,也就是說如果沒有自定義過類加載器,同時它也是getSystemClassLoader() 的返回值.

    這幾種類加載器的工作流程被抽象成一個模型,就是雙親委派模型.

    ?

    工作流程:

  • 收到類加載的請求
  • 首先不會自己嘗試加載此類,而是委托給父類的加載器去完成.
  • 如果父類加載器沒有,繼續尋找父類加載器.
  • 搜索了一圈,發現都找不到,然后才是自己嘗試加載此類.
  • 這基本就是雙親委派模型.

    但是這種模型只是一種推薦的方式,并不是強制的,你也可以嘗試打破這種規則.
    自所以這樣約定,還是有一定的好處的, Java 類隨著它的類加載器一起具備了一種帶有優先級的層次關系.
    比如自己定義了java.lang.Object 對象,那么按照上面的流程,他永遠都是被啟動類加載器加載的rt.jar 中的那個類,而不是自己定義的這個類,這樣就保證了兄運行的穩定,否則,可能變得非常混亂,可以隨意改寫任何類.

    在 JavaAgent 中的應用

    大多數情況下,其實我們并不需要知道這些,因為你的程序也會運行的非常正常,雖然像Tomcat,Spring Boot 都有自己定義的類加載器,但是我們在不用關心的情況下也會運行的好好地.

    那么類加載器可以被運行在哪些地方呢?

    • 從遠程(或者文件)加載類,有時候需要加載的類可能并不是在當前的 classpath, 可能需要自己定義類加載器去加載.
    • 自己想實現一個JavaAgent來增強字節碼的時候.

    JavaAgent 的使用后續文章補上.先上一張圖.

    ?

    • 頂層是應用代碼實際運行的 ClassLoader, 可能是Application ClassLoader, 也有可能是 tomcat 的webapp ClassLoader 或者其他容器自定義的類加載器,總是是真實 的用戶編寫的代碼運行的 classloader.

    • 我們如果要在javaagent中增強用戶或者用戶使用的包進行增強的話,必須實現一個自定義的 classloader 來"繼承"(委派)應用代碼的類加載器.為什么?

    • javaagent 的代碼永遠都是被應用類加載器( Application ClassLoader)所加載,和應用代碼的真實加載器無關,舉個栗子,當前運行在 tomcat 中的代碼是webapp ClassLoader 加載的,如果啟動參數加上-javaagent, 這個 javaagent 還是在Application ClassLoader中加載的.

    • 按照上面的雙親委派模型,如果我們在 javaagent 中想要訪問應用里面的 api 包或者類,這是不可能的,因為按照雙親委派模型,通俗來說就是,子加載器可以訪問父加載器中的類,但是反過來就行不通.

    那么這個時候有沒有辦法能夠做到呢?

    • 我們可以自定義自己的類加載器繼承應用代碼類加載器(可以在 javaagent 中完成, javaagent 每加載一個類,就會回調傳回真實的類加載器),然后我們在Application ClassLoader 中用自定義的類加載器去加載子類,并創建好實例(newInstance()), 將實例的引用保存 在變量中.

    • 真實運行的時候,就會通過這個變量,去訪問我們自定義加載器的內容,又由于我們的自定義類加載器是繼承自應用代碼的類加載器的,所以自定義類加載器中的代碼可以訪問應用的代碼.

    總結一句就是,父類加載器無法加載子類加載器的類,但是可以持有子類加載器所加載類的實例,從而實現父類加載器的代碼可以調用子類加載器的代碼的形式

    貌似比較抽象,后面會補上詳細的例子供參考.

    例子

    針對上面的情形,我們定義一個例子,可以詳細解釋 ClassLoader 的加載使用,

  • 假如我們有如下的 ClassLoader,FooClassLoader:
  • package com.example.test;import java.io.File; import java.io.FileInputStream; import java.io.IOException;/*** @author lican*/ public class FooClassLoader extends ClassLoader {private static final String NAME = "/Users/lican/git/test/foo/";@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {Class<?> loadedClass = findLoadedClass(name);if (loadedClass == null) {String s = name.substring(name.lastIndexOf(".") + 1) + ".class";File file = new File(NAME + s);try (FileInputStream fileInputStream = new FileInputStream(file)) {byte[] b = new byte[fileInputStream.available()];fileInputStream.read(b);return defineClass(name, b, 0, b.length);} catch (IOException e) {e.printStackTrace();}}return loadedClass;}}
  • 被加載的類定義,然后我們將這個類放到不是源代碼的路徑比如我放到
    /Users/lican/git/test/foo/這里的,主要是方便測試.
  • package com.example.test;public class FooTest {public String getFoo() {return "foo";} }

    然后測試程序為:

    package com.example.test;import java.lang.reflect.Method;/*** @author lican*/ public class ClassLoaderTest {private Object fooTestInstance;private FooClassLoader fooClassLoader = new FooClassLoader();public static void main(String[] args) throws Exception {ClassLoaderTest classLoaderTest = new ClassLoaderTest();classLoaderTest.initAndLoad();Object fooTestInstance = classLoaderTest.getFooTestInstance();System.out.println(fooTestInstance.getClass().getClassLoader());Method getFoo = fooTestInstance.getClass().getMethod("getFoo");System.out.println(getFoo.invoke(fooTestInstance));System.out.println(classLoaderTest.getClass().getClassLoader());}private void initAndLoad() throws Exception {Class<?> aClass = Class.forName("com.example.test.FooTest", true, fooClassLoader);fooTestInstance = aClass.newInstance();}public Object getFooTestInstance() {return fooTestInstance;} }

    我們用FooClassLoader來加載com.example.test.FooTest, 然后在 AppClassLoader中持有引用.被后續使用.

    引用

    • 深入理解 Java 虛擬機(第二版)

    ?

    總結

    以上是生活随笔為你收集整理的深入理解Java ClassLoader及在 JavaAgent 中的应用的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。