<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      JVM深入學習-ClassLoader篇(一)

      初識JVM --- ClassLoader深入理解

      ClassLoader、SPI機制

      Class對象的理解

      java在誕生之初,就有一次編譯到處運行的名言,今天我們來探究一下,從java代碼到class到運行,JVM中的ClassLoader充當一個什么樣的角色。
      

      一個簡單的JVM流程圖(簡單了解)

      image-20230925151815035
      流程圖.jpg

      從位置角度理解JVM:就JVM在物理結構上的位置而言,它與Java的傳統理念"一次編譯,到處運行"密切相關。我們的計算機操作系統(例如Windows、Linux和Mac)本質上都是一系列軟件的組合,而JVM同樣也是基于操作系統的軟件之一。因此,從這個角度來看,可以很容易地理解JVM跨平臺的原因:每個操作系統都可以安裝一個JVM軟件,從而使Java程序能夠在不同平臺上運行。

      JVM是存在于操作系統之上的軟件,具體存在于操作系統上的JRE構建環境中

      image-20231102081727655
      JVM位置.jpg

      JVM流程圖:建議自己畫一下簡單的流程圖(在JVM流程中了解ClassLoader的位置)

      image-20231102082210361

      JVM詳細的流程圖:在學習JVM之前,我建議你淺淺看一下,流程圖中所涉及的每一個知識的概念。

      注意:只需要淺淺看一下,知道是每一步都是干啥的???
      
      image-20231102083110541

      學前問題 ?

      以下問題的答案并不唯一,目的是為了引出一些知識!?。?/p>

      1. 一個Java代碼在運行時,需要編譯幾次?

      在Java代碼運行時,通常需要進行兩次編譯。首先,使用javac進行前端編譯,將Java源代碼編譯成字節碼文件,以便進行類加載和執行。其次,通過JIT(即時編譯)后端編譯,將HotSpot代碼編譯成本地機器碼,以提高代碼的執行速度。

      1. Method Area 你是否了解?

      方法區(Method)是Java虛擬機的內存區域之一。主要是用于存儲類的結構信息、運行時常量池、字段、和方法描述、靜態變量等元數據。下面是其中的一些常見的存儲內容:

      1. 類的結構信息:這包括類的字段、方法、構造方法,以及類的繼承關系、接口實現等結構信息。
      2. 運行時常量池:運行時常量池是類文件中常量池的運行時表示,它包含類中使用的字面常量、符號引用、方法和字段引用等。這些信息在運行時可以被解析為直接引用。
      3. 字段和方法描述:方法區存儲了類中各個字段和方法的描述信息,包括字段的數據類型、訪問修飾符、方法的參數列表和返回類型等。
      4. 靜態變量:靜態變量,即使用static關鍵字聲明的類級別變量,也會被存儲在方法區中,并在類的初始化階段進行分配和初始化。
      5. 類的字節碼:類的字節碼文件,即編譯后的.class文件,其中包含了類的方法體、指令集等定義,這些字節碼會被加載到方法區供執行。
      6. 異常處理表:方法區還會存儲異常處理信息,包括異常處理代碼的偏移位置、異常類型等,用于異常處理。

      需要注意的是,Java虛擬機的具體實現可以有不同的內存管理方式,例如使用永久代(在Java 7及之前的HotSpot虛擬機中)或使用元數據區(在Java 8及以后的HotSpot虛擬機中)。在Java 8及之后,方法區已經不再被稱為"方法區",而被替代為"元數據區"(Metaspace),它采用了不同的內存管理方式,如使用本機內存,而不再有固定的區域大小限制。

      3.class關鍵字和Class對象的區別?

      class就是Java的一個關鍵字,用于聲明一個類,比如 public class Student 使用class關鍵字聲明了一個類。
      
      Class是存在于java.lang.Class的一個類,這個類是用于描述類與接口meta信息的、用于支持反射的一種類型。
      //用于支持反射操作的類型:反射是指在運行時檢查、查詢和修改類的屬性和方法。Class類是Java反射的關鍵,它允許程序在運行時獲取類的信息,創建類的實例,調用類的方法等。因此,Class類被描述為一種用于支持這種反射操作的類型。
      
      //Class只是一個名字比較特殊的類,是關鍵字class修飾的類,一般應用于反射,只是名稱比較特殊而已,可以通過Class類型來獲取其他類型的元數據(metadata),比如字段,屬性,構造器,方法等等,可以獲取并調用。
      

      image-20231117082615332

      image-20231117082447006

      Class對象的理解

      Class對象理解

      到文章這里你應該了解到JVM框架流程圖中一些概念,比如:javac是什么?classLoader是干什么的?Class對象是什么?ClassLoader類加載系統的生命周期有那些部分?
      

      4 . ClassLoader類加載系統做了什么?

      我們通過類加載器(加載階段)去加載特定的字節碼文件。在加載階段我們使用雙親委派機制去處理class,并將獲取的二進制字節流轉換為方法區的數據結構(這種數據結構包含了類的字段、方法、常量池)。然后在堆區對應生成一個Class對象,允許程序在運行時通過該對象去訪問和處理類的相關信息。緊接著就是驗證這個信息是否有問題。在這里我們會進行一些文件格式的校驗、元信息是否有問題、符號使用是否正常等等。在準備階段,處理靜態變量、在解析階段,將使用的符號等轉換為真實的內存地址。最后進行初始化,對我們之前的靜態變量賦值,并且運行static{ System.out.println('"你好啊"')} 靜態代碼塊。

      至此,整個類加載系統就完成了,我們在加載階段將字節碼加載到JVM的內容中,在其他階段對這個加載的信息進行驗證和處理然后交給JVM運行時區域。

      類的加載機制

      作用:類加載器主要負責將編譯后的字節碼類文件(存放在磁盤的二進制數據),載入到JVM的內存中, 并將其放在方法區中,然后在堆中創建一個java.lang.Class對象,用來封裝類在方法去內的數據結構,在成功裝載到內存中之后,就需要堆數據進行 校驗、轉換解析和初始化,最終形成被虛擬機使用的java類型。

      在堆區創建一個 java.lang.Class對象":在加載類時,JVM會在堆內存中創建一個java.lang.Class類的實例,該實例用來代表加載的類,并允許程序在運行時通過反射等方式訪問類的結構信息。

      類生命周期

      image-20231102085907074

      ClassLoader Oncreate

      類加載的過程,包括了加載、驗證、準備、解析、初始化五個階段。

      加載

      在加載階段主要做三件事

      1、獲取字節碼流:類加載器根據類的完全限定名(例如com.example.nzp)來查找和獲取表示該類的二進制字節流。

      2、轉化為方法區結構 :類加載器將獲取到的字節碼流轉化為方法區(新版的Java虛擬機中稱為元空間)中的數據結構。這些數據結構包括了類的字段、方法、常量池等信息,用于描述類的結構和特性。

      3、生成class對象:在堆內存中生成一個代表加載的類的Java.lang.Class對象。這個Class對象充當了訪問方法區中的數據的入口點,允許程序在運行時通過反射等方式訪問類的信息。

      驗證

      驗證的主要作用就是確保被加載的類的正確性,如果類文件未通過驗證,加載過程就會失敗,并拋出'java.lang.VerifyError'異常。

      1、文件格式驗證(File Format Verification):首先虛擬機會對類文件的格式進行校驗,確保它遵守Java虛擬機規范。這包括檢查類文件的魔數、版本號、字段、常量池、方法表等部分。

      2、元數據校驗(Metadata Verification):在這一步,虛擬機會檢查類的元數據信息,包括類、字段、方法的訪問修飾符是否正確、類的繼承關系是否合法等等。這有助于確保類的結構在語義上是正確的。

      3、字節碼驗證(Bytecode Verification):虛擬機會對字節碼進行驗證,以確保它遵循類安全性規則,不會導致數組越界、類型轉換錯誤等等。這是確保程序不會因為惡意代碼而受到攻擊的重要步驟。

      3、符號引用驗證(Symbolic Reference Verification): 這一步驗證類中的符號引用是否能夠正確被解析,例如檢查類、字段和方法是否都能找到對應的定義。

      準備

      準備階段主要是為類變量分配內存并設置初始值。

      1、為類變量(static)分配內存:在準備階段,虛擬機為類中的靜態變量分配內存空間。這是在方法區中完成的,方法區用于存儲類的結構信息和靜態變量。

      2、初始值:在該階段,靜態變量會被賦予初始值,這些默認值是數據類型的默認值,而不是代碼中顯示賦予的值。例如,整數類型的靜態變量被賦予的默認值就是0,布爾值的默認值是false,引用類型的默認值為null。

      public static int a = 1;
      //這行代碼在準備階段過后的 a 值為 0 ,顯示賦值是在初始化階段進行的。
      
      public static boolean test;
      //默認值為false
      
      
      注意:前面的a值被static所修飾的,在準備階段為0 ,但如果是被static 和 final同時修飾,public static final int a = 1;
      那么這個值在準備階段就會是1了。
      
      對于static final修飾的常量,在編譯階段會被優化,并且它們的值會被直接存放在調用它們的類的常量池中。這個優化是在編譯器進行的,而不是在類加載的準備階段。
      

      解析

      解析階段主要是將虛擬機常量池中的符號引用轉化為直接引用的過程。

      在解析過程中,Java虛擬機會查找常量池中的符號引用,然后將其映射到實際內存地址或偏移量。這使得程序可以正確地訪問和執行類、字段、方法等,而不受符號引用的抽象性和限制。

      我們使用一個例子來理解 "符號引用 "轉換為 "直接引用 "的過程:

      public class MyClassExample {
          public void myMethod{
              System.out.println("hello");
          }
      }
      
      //在這個類中,我們有一個myMethod方法,它調用了System.out.println方法。當MyClassExample 類被加載和初始化時,需要進行解析以確定System.out.println方法的實際位置,這才可以讓class類正確的找到并調用它
      符號引用:在`myMethod`中的`System.out.println`是一個符號引用,它是一個抽象的引用,不包含實際的內存地址。它只包含方法的名稱、參數類型和返回類型等信息。
          
      解析:在解析階段,Java虛擬機會查找System.out.println()的實際內存地址,并將其轉化為直接引用。(在這里涉及查找System類,查找println方法,并確定方法的入口地址)。
          
      直接引用:解析完成后,System.out.println就會被轉換為實際的內存地址,當我們調用myMethod方法時,Java虛擬機就可以使用已解析的直接引用,跳轉到System.out.println方法的內存地址,執行該方法,輸出hello。
      

      解析動作主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調用點限定符7類符號引用進行

      類或接口符號引用:用于解析類或接口的全限定名,以確定類或接口的位置。
      
      字段符號引用:用于解析字段的名稱和類型,以確定字段的內存布局和位置。
      
      類方法符號引用:用于解析靜態方法的名稱和參數類型,以確定方法的入口地址。
      
      接口方法符號引用:用于解析接口方法的名稱和參數類型,以確定方法的入口地址。
      
      方法類型符號引用:用于解析方法類型(方法的參數類型和返回類型),以確定方法類型的描述信息。
      
      方法句柄符號引用:用于解析方法句柄,以確定方法句柄的類型和目標。
      
      調用點限定符符號引用:用于解析調用點限定符,以確定方法調用的目標方法和接收者類型。
      

      初始化

      主要是用來確保類的靜態成員(靜態變量和靜態初始快)在首次使用之前已經被正確初始化,以保證類的正確性和可用性。一句話描述這個階段就是執行類構造器< clinit >()方法的過程。

      1、執行類構造器"()方法" :初始化階段會執行由編譯器生成的類構造器方法,它負責對類的靜態成員進行初始化。這包括靜態變量的賦值和靜態初始化塊中的代碼執行。

      2、初始化靜態變量:靜態變量會被分配內存并設置為初始值。如果靜態變量在類的生命中被顯式初始化,這些顯式復制的操作也會在初始化進行。

      3、調用類的構造函數:如果類具有顯式的構造函數,構造函數也會在初始化階段執行。這通常發生在類的靜態初始化塊之后。

      4、確保類的一致性:初始化確保類的所有靜態成員都已被正確的初始化,以便在類執行使用時不會出現未初始化的情況。

      實例:

      //類初始化時機:只有當對類的主動使用的時候才會導致類的初始化。
      //類的主動使用包括以下六種:
      1.創建類的實例,也就是new的方式
      2.訪問某個類或接口的靜態變量,或者對該靜態變量賦值
      public class Demo {
          static{
              System.out.println("進行初始化");
          }
          //訪問簡單靜態變量x,會導致Demo類的初始化。
          public static int x = 10;
      }
      
      3.調用類的靜態方法
      public class Demo{
          static{
              System.out.println("進行初始化");
          }
          //在其他類中調用靜態方法test,會導致Demo類的初始化。
          public static void test(){
          }
      }
      
      4.反射(如 Class.forName(“com.shengsiyuan.Test”))
      public static void main(String[] args) throws ClassNotFoundException {
              //通過反射導致Demo類初始化
              Class.forName("co.youzi.test.Demo");
          }
          
      5.初始化某個類的子類,則其父類也會被初始化
      注意:通過子類使用父類的靜態變量只會導致父類的初始化,子類不會初始化。
      public class Parent {
          static{
              System.out.println("父類初始化");
          }
          public static int x = 10;
      }
      public class Child extend Parent{
          static{
              System.out.println("子類初始化");
          }
          public static int y = 100;
      }
      public class Test {
          public static void main(String[] args) throws ClassNotFoundException {
              System.out.println(Child.y);//輸出 父類初始化 子類初始化 100
              //System.out.println(Child.x);//輸出 父類初始化 10
          }
      }
      6. Java虛擬機啟動時被標明為啟動類的類( JavaTest),直接使用 java.exe命令來運行某個主類
          
      //被動引用:Java的類加載機制中存在被動引用的概念,即在不觸發類的初始化的情況下引用類。這是因為在某些情況下,你可能只是希望使用類的一部分功能而不需要完全初始化這個類。以下是一些例子:
      
      1.通過子類引用父類的靜態字段,不會導致子類初始化。
      2.通過數組定義來引用類,不會觸發此類的初始化。MyClass[] cs = new MyClass[10];
      public class Test05 {
          public static void main(String[] args) throws ClassNotFoundException {
              //Parent類不會被初始化
              Parent[] parent = new Parent[10];
              System.out.println(parent.length);
          }
      }
      3.常量在編譯階段會存入調用類的常量池中,本質上并沒有直接引用到定義常量的類,因此不會觸發定義常量的類的初始化。
      public class Simple {
          static{
              System.out.println("進行初始化");
          }
          //訪問靜態常量MAX不會導致Simple類的初始化
          public final static int MAX = 10;
          //雖然RANDOM是靜態常量,但是由于計算復雜,只有初始化后才能得到結果,因此其他類使用RANDOM會導致Simple類的初始化
          public final static int RANDOM = new java.util.Random(10).nextInt();
      }
      
      

      ClassLoader

      類加載器(ClassLoader)負責將字節碼文件加載到內存中,并將其轉換為運行時的類對象,以便JVM執行字節碼時使用。

      類加載器的層次結構

      雙親委派模型是Java中的一種類加載機制,其核心思想就是除了頂層加載器沒有父類加載器之外(BootStra ClassLoader),其他所有的類加載器都有自己的父類加載器。這就意味著在類加載的過程中,一個類加載器首先會嘗試將加載請求委派給父類加載器,只有父類類加載器無法完成加載時,子類加載器才會去加載。
          
      //繼承關系: 在傳統的繼承關系中,子類繼承父類的行為和屬性。但在類加載器的情境中,由于類加載器的設計需要保持獨立性和隔離性,直接使用繼承關系可能會導致耦合度過高,因為類加載器之間并不需要繼承關系中的方法和屬性。組合關系: 相反,使用組合關系,子類加載器持有一個對父類加載器的引用,可以更靈活地復用父加載器的加載邏輯,而不必繼承其具體實現。這種組合關系允許子加載器委托給父加載器,并且在必要時可以自定義加載行為。
      

      從java虛擬機的角度來講,只存在兩種不同的類加載器:一種是啟動類加載器(BootStrap ClassLoader),這個類加載器是用C++實現的,是虛擬機自身的一部分,另一種就是其他所有的類加載器,這些類加載器都是由java語言實現的,是獨立于虛擬機外部,并且全部繼承自 java.lang.ClassLoader。

      從程序員的角度來講,類加載器可以劃分的更為細致,有以下四種(其中Custom ClassLoader加載器是根據需求去自定義的一個類加載器,非特殊場景,不需要):

      四種加載器

      1

      啟動類加載器:Bootstrap ClassLoader

      這個類加載器使用C/C++語言實現,嵌套在JVM中,Java程序是無法直接操作該類。它用來加載Java的核心類庫,如:JAVA_HOME/jre/lib/rt.jar 、resource.jar路徑下的包,用于提供jvm運行所需要的包。

      并不是繼承自java.lang.ClassLoader,sun.boot.class.path它沒有父類加載器

      擴展類加載器:Extension ClassLoader

      Java語言編寫,由sun.misc.Launcher$ExtClassLoader實現,我們可以用Java程序操作這個加載器繼承自Java.lang.ClassLoader,父類加載器為啟動類加載器。它用來加載jre/lib/ext目錄下的類庫。我們就可以將我們自己的包放在以上目錄下,就會自動加載進來了。

      應用程序加載器:Appliacation ClassLoader

      Java語言編寫,由sun.misc.Launcher$AppClassLoader實現。

      繼承自java.langClassLoader,父類加載器為擴展類加載器。她負責加載環境變量classpath或者系統屬性java.class.path指定路徑下的類庫。

      它是程序中默認的類加載器,我們Java程序員中的類,都是由它加載完成的。

      自定義加載器:Custom ClassLoader

      當上述 3 種類加載器不能滿足開發需求時,用戶可以自定義類加載器

      自定義類加載器時,需要繼承ClassLoader類。如果不想打破雙親委派模型,那么只需要重寫findClass方法即可;如果想打破雙親委派機制,就打破loadClass方法;

      除了啟動類加載器,其他三種類加載器都繼承自 java.lang.ClassLoader 抽象類。其源碼如下:

      
      public abstract class ClassLoader {
      
          //聲明一個靜態的本地方法 registerNatives。(由本地的非Java代碼提供)
          private static native void registerNatives();
          //在類加載的初始化過程中執行靜態代碼塊中的registerNatives方法。
          //該方法實現由底層的本地代碼提供。由C/C++等語言實現,并通過JNI(Java Native Interface)與java代碼進行交互。
          //這段代碼的目的是為 ClassLoader 類注冊一個本地方法,并在類加載時調用該本地方法。通過本地方法,Java代碼可以調用由底層本地代碼提供的功能,從而實現與底層系統或硬件的交互。
          
          static {
              registerNatives();
          }
      
      
          private final ClassLoader parent;
      
          protected Class<?> findClass(String name) throws ClassNotFoundException {
              throw new ClassNotFoundException(name);
          }
      
          protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
              
              Class c = findLoadedClass(name);
              if (c == null) {
                  try {
                      if (parent != null) {
                          c = parent.loadClass(name, false);
                      } else {
                          c = findBootstrapClass0(name);
                      }
                  } catch (ClassNotFoundException e) {
                      c = findClass(name);
                  }
              }
              if (resolve) {
                  resolveClass(c);
              }
              return c;
          }
      }
      
      

      觀察類加載器的整條鏈

      public class res {
          public static void main(String[] args) {
      
              res res = new res();
              ClassLoader classLoader = res.getClass().getClassLoader();
              System.out.println(classLoader);
              System.out.println(classLoader.getParent());
              System.out.println(classLoader.getParent().getParent());
          }
      }
      //sun.misc.Launcher$AppClassLoader@18b4aac2
      //sun.misc.Launcher$ExtClassLoader@1b6d3586
      //null    這里的null是因為BootStrap是C++實現的,無法使用Java代碼去調用該類加載器
      

      自定義類加載器

      我們自定義類加載器里面重寫了loadClass方法,在Main類中通過while循環輸出當前及其父類類加載器。

      輸出結果中沒有BootStrap ClassLoader是因為它是C++實現的,我們無法通過java代碼去調用它

      package ClassLoader;
      
      import java.io.IOException;
      import java.io.InputStream;
      
      public class ConsumerClassLoaderDemo extends ClassLoader {
      
          public static void main(String[] args) throws Exception {
      
              ClassLoader myClassLoader = new ConsumerClassLoader();
              Object obj = myClassLoader.loadClass("ClassLoader.data").newInstance();
              ClassLoader classLoader = obj.getClass().getClassLoader();
              // BootStrapClassLoader在Java中不存在的,因此會是null
              while (null != classLoader) {
                  System.out.println(classLoader);
                  classLoader = classLoader.getParent();
              }
          }
      }
      
      class ConsumerClassLoader extends ClassLoader {
          @Override
          public Class<?> loadClass(String name) throws ClassNotFoundException {
              try {
                  String classFile = name.substring(name.lastIndexOf(".") + 1) + ".class";
                  InputStream in = getClass().getResourceAsStream(classFile);
                  if (null == in) {
                      return super.loadClass(name);
                  }
                  int count = 0;
                  while(count == 0){
                      count = in.available();
                  }
                  byte[] bytes = new byte[count];
                  in.read(bytes);
                  return defineClass(name, bytes, 0, bytes.length);
              } catch (IOException e) {
                  throw new ClassNotFoundException(name);
              }
          }
      }
      
      

      輸出:

      ClassLoader.ConsumerClassLoader@74a14482
      sun.misc.Launcher$AppClassLoader@18b4aac2
      sun.misc.Launcher$ExtClassLoader@677327b6
      

      雙親委派機制

      什么是雙親委派機制?

      JVM中,類加載器默認使用雙親委派原則。

      1.如果一個類加載器收到了類加載請求,它并不會自己先加載,而是把這個請求委托給父類的加載器去執行。
      2.如果父類加載器還存在其父類加載器,則進一步向上委托,依次遞歸,請求最終將到達頂層的引導類加載器 BootstrapClassLoader。
      3.如果父類加載器可以完成類加載任務,就成功返回;倘若父類加載器無法完成加載任務,子加載器才會嘗試自己去加載。
      4.父類加載器一層一層往下分配任務,如果子類加載器能加載,則加載此類;如果將加載任務分配至系統類加載器(AppClassLoader)也無法加載此類,則拋出異常。
      
      雙親?

      classloader 類存在一個 parent 屬性,可以配置雙親屬性。默認情況下,JDK 中設置如下。

      ExtClassLoader.parent=null;
      
      AppClassLoader.parent=ExtClassLoader
      
      //自定義
      XxxClassLoader.parent=AppClassLoader
      
          
          
          
      在雙親委派模型中,一般情況下確實是ExtClassLoader的父加載器為Bootstrap Class Loader,AppClassLoader的父加載器為ExtClassLoader。但是,ClassLoader的parent屬性是private的,直接設置它的值是不允許的。實際上,這個關系是在ClassLoader的構造函數中初始化的。
      
      如果你要自定義一個類加載器,例如XxxClassLoader,并且希望它的父加載器為AppClassLoader,你應該通過構造函數來指定:
      
      public class XxxClassLoader extends ClassLoader {
          public XxxClassLoader() {
              super(AppClassLoader.getSystemClassLoader());
              // 其他初始化工作...
          }
      }
      這樣,你就通過構造函數顯式地將AppClassLoader設置為了XxxClassLoader的父加載器。在這種情況下,ExtClassLoader并沒有直接參與到XxxClassLoader的層級結構中。
      
      
      委派?

      委派就是ClassLoader類加載過程中的處理邏輯,是通過 java.lang.ClassLoader 類的loadClass方法實現的。

      java.lang.ClassLoader.loadClass()

      
      public abstract class ClassLoader {
      
          //聲明一個靜態的本地方法 registerNatives。(由本地的非Java代碼提供)
          private static native void registerNatives();
          //在類加載的初始化過程中執行靜態代碼塊中的registerNatives方法。
          //該方法實現由底層的本地代碼提供。由C/C++等語言實現,并通過JNI(Java Native Interface)與java代碼進行交互。
          //這段代碼的目的是為 ClassLoader 類注冊一個本地方法,并在類加載時調用該本地方法。通過本地方法,Java代碼可以調用由底層本地代碼提供的功能,從而實現與底層系統或硬件的交互。
          
          static {
              registerNatives();
          }
      
          private final ClassLoader parent;
      
          protected Class<?> findClass(String name) throws ClassNotFoundException {
              throw new ClassNotFoundException(name);
          }
      //委派的實現重點就是這里
          protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
              
              Class c = findLoadedClass(name);
              if (c == null) {
                  try {
                      if (parent != null) {
                          c = parent.loadClass(name, false);
                      } else {
                          c = findBootstrapClass0(name);
                      }
                  } catch (ClassNotFoundException e) {
                      c = findClass(name);
                  }
              }
              if (resolve) {
                  resolveClass(c);
              }
              return c;
          }
      }
      
      
      loadClass、findClass、defineClass方法

      關于類加載的三個方法的講解:

      loadClass() :雙親委派的實現邏輯就是通過方法

      findClass() :根據名稱或位置加載 .class字節碼

      defineClass() :把.class字節碼轉換為Class對象,就是類加載過程中的loading

      雙親委派機制的優缺點

      雙親委派可以保證一個類不會被多個類加載器重復加載,并且保證核心 API 不會被篡改。

      類的唯一性: 雙親委派模型通過在類加載器層次中使用父子關系,確保了在一個Java虛擬機實例中,任何一個類都只會被加載一次。這避免了類的重復加載,提高了系統的內存利用率。
      
      安全性: 雙親委派模型可以防止惡意類的加載。由于類加載是從父加載器向子加載器委派的,所以如果一個類已經被父加載器加載,子加載器就沒有機會重新加載,避免了惡意類替換的可能性。(保證了核心的API的使用)
      
      層次性: 類加載器之間形成了層次結構,每個加載器都有一個明確定義的父加載器。這種層次結構有助于更好地組織和管理類的加載過程。例如,應用程序的類可以由應用程序類加載器加載,擴展的類由擴展類加載器加載,核心的Java類由啟動類加載器加載。
      
      代碼隔離: 每個類加載器都有自己的命名空間,一個加載器加載的類對于其它加載器是不可見的。這種隔離性有助于防止不同模塊之間的類名沖突。
      
      性能提升: 雙親委派模型在加載類時,先由父加載器嘗試加載,只有在父加載器無法完成加載時才由子加載器嘗試加載。這樣可以避免重復加載,提高了類加載的效率。
      

      缺點:

      靈活性受限: 雙親委派模型在一定程度上限制了類加載的靈活性。在某些場景下,比如需要實現類的熱替換、動態代碼生成等,這種限制可能顯得不夠靈活。
      
      資源浪費: 由于每個類加載器都要委托給父加載器,可能會導致一些資源浪費。在一個多層級的類加載器結構中,如果某個類加載器在加載類時不進行適當的緩存,可能會導致多次加載相同的類,浪費內存。
      
      性能開銷: 雙親委派模型在類加載的時候需要依次向上委托,直到達到啟動類加載器。這個過程會引入一定的性能開銷,尤其是在類加載器層次比較深或者類加載器之間的委托鏈較長的情況下。
      
      自定義類加載器復雜性: 如果需要自定義類加載器,特別是在需要實現一些高級功能時,雙親委派模型的約束可能會增加實現的復雜性。
      
      無法實現類的版本隔離: 雙親委派模型不能很好地處理同一類的不同版本的情況。在某些場景下,不同的應用程序可能需要加載相同包名下的不同版本的類,而雙親委派模型可能無法滿足這種需求。
      

      打破雙親委派機制

      雙親委派模型并不是一個強制性約束,而是 Java 設計者推薦給開發者的類加載器的實現方式。在一定條件下,為了完成某些操作,可以 “打破” 模型。

      重寫loadClass方法: 自定義類加載器可以通過重寫loadClass方法來改變默認的加載行為。在loadClass方法中,你可以自行決定是否委派給父加載器或者直接加載類。

      public class MyClassLoader extends ClassLoader {
          @Override
          public Class<?> loadClass(String name) throws ClassNotFoundException {
              // 自定義加載邏輯,可以選擇是否委派給父加載器
              // ...
              return super.loadClass(name);
          }
      }
      

      獨立加載特定類: 如果只想打破雙親委派模型加載某個特定類,可以使用findClass方法,該方法在默認實現中拋出ClassNotFoundException,但你可以重寫它以實現自定義加載邏輯。

      public class MyClassLoader extends ClassLoader {
          @Override
          protected Class<?> findClass(String name) throws ClassNotFoundException {
              // 自定義加載邏輯,加載指定類
              // ...
              return super.findClass(name);
          }
      }
      

      設置父加載器為null 在創建自定義類加載器的時候,可以通過構造函數顯式地將父加載器設置為null,這將使得自定義加載器成為一個頂級加載器,不再委派給父加載器。

      public class MyClassLoader extends ClassLoader {
          public MyClassLoader() {
              super(null); // 設置父加載器為null
          }
      }
      

      利用線程上下文加載器:使用Thread.currentThread().setContextClassLoader在某些場景下,可以通過設置線程上下文類加載器(Context Class Loader)來影響類加載的行為。這樣,線程在加載類時將會使用設置的上下文類加載器,而不再受限于雙親委派模型。

      import java.io.InputStream;
      import java.util.Properties;
      
      public class ThreadContextClassLoaderExample {
          public static void main(String[] args) throws Exception {
              // 資源名稱
              String resourceName = "nzp.test";
      
              // 獲取當前線程的上下文類加載器
              ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
      
              // 使用線程上下文類加載器獲取資源輸入流
              InputStream inputStream = contextClassLoader.getResourceAsStream(resourceName);
      
              if (inputStream != null) {
                  // 如果找到資源,加載并處理
                  Properties properties = new Properties();
                  properties.load(inputStream);
      
                  // 輸出讀取的配置信息示例
                  properties.forEach((key, value) -> System.out.println(key + ": " + value));
              } else {
                  // 如果資源未找到
                  System.out.println("Resource not found: " + resourceName);
              }
          }
      }
      
      
      posted @ 2023-11-20 13:54  Liberty碼農志  閱讀(408)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 日本三级香港三级人妇99| 中文国产成人精品久久不卡| 日本三级香港三级三级人妇久 | 亚洲gay片在线gv网站| 午夜精品极品粉嫩国产尤物| 中文文精品字幕一区二区| 午夜福利看片在线观看| 日韩中文字幕精品人妻| 老熟妇乱子交视频一区| 二区三区亚洲精品国产| 女人爽到高潮的免费视频| 国产初高中生粉嫩无套第一次| 一区二区三区国产综合在线 | 国产成人综合在线观看不卡| 黑人好猛厉害爽受不了好大撑| 日韩一区二区三区精品区| 扒开粉嫩的小缝隙喷白浆视频| 2021国产精品视频网站| 国产亚洲av产精品亚洲| 国产在线播放专区av| 国99久9在线 | 免费| 国产一区二三区日韩精品| 亚洲精品一区三区三区在| 国产果冻豆传媒麻婆精东| 亚洲av日韩av一区久久| 久久精品国产亚洲成人av| 亚洲日本va午夜在线影院| 无套内谢少妇毛片在线| 国产仑乱无码内谢| 肥大bbwbbw高潮抽搐| 东方四虎在线观看av| 国产视频一区二区在线看| 岛国av在线播放观看| 国厂精品114福利电影免费| 狠狠综合久久av一区二| 漂亮人妻被修理工侵犯| 国产成人午夜福利院| 高颜值午夜福利在线观看| 久久天天躁夜夜躁狠狠| 免费 黄 色 人成 视频 在 线| 国产欧美在线手机视频|