JVM-1 JVM學習筆記

JVM學習筆記

一、類加載

  1. 在java代碼中,類型的加載、連接與初始化過程都是在程序運行期間完成的

    • 類型加載:查找並加載類的二進制數據。最常見的情況,將已經編寫好的class文件從磁盤加載到內存。

    • 連接: 將類與類之間的關係確定好,並且對於字節碼的一些相關的處理,對於字節碼的驗證都是在連接過程處理(類與類的符號引用轉化成直接引用)。

      • 驗證:確保被加載的類的正確性

      • 準備:爲類的 靜態變量 分配內存,並將其初始化爲默認值

        class Test{
          // 注意:在準備階段 a 這裏的初始化值不是1 而是0
          public static int a = 1; 
        }
        
 - 解析:把類中的符合引用轉化爲直接引用
  • 初始化過程:如對於類中的一些靜態變量進行賦值,在初始化階段完成的。

    class Test{
      // 注意:在初始化階段 a 這裏才被賦予 1 這個值
      public static int a = 1;
    }
    
  1. 提供了更大的靈活性,增加了更多的可能性

二、類加載器

  1. java虛擬機與程序的生命週期
  2. 在如下幾種情況下,java虛擬機將結束生命週期
    • 在執行了System.exit()方法
    • 程序正常執行結束
    • 程序在執行過程中遇到異常或錯誤而異常終止
    • 由於操作系統出現錯誤而導致java虛擬機進程終止

三、類的加載、連接與初始化

  1. java程序對的使用方式可分爲兩種

    • 主動使用

      • 創建類的實例

      • 訪問某個類或接口的靜態變量,或者對該靜態變量賦值(字節碼層面:訪問靜態變量 getstatic助記符 靜態變量賦值 putstatic助記符 完成類的操作)

      • 調用類的靜態方法(字節碼層面:invokestatic 助記符來完成操作)

      • 反射 如 :

        Class.forName("com.test.Test")
        
      • 初始化一個類的子類

        class Parent{}
        // 當對 Child進行初始化時 也會對Parent進行初始化 如果Parent上也有父類 以此類推
        class Child extends Parent{}
        
 - java虛擬機啓動時被標明爲啓動類的類(Java Test 包含main 方法時)

 - JDK1.7開始提供的動態語言支持:java.lang.invoke.MethodHandle實例的解析結果 REF_getStatic,REF_putStatic,REF_invokeStatic句柄對應的類沒有初始化則初始化
  • 被動使用(除了以上七種情況,其他使用java類的方式都被看作是對類的被動使用,都不會導致類的 初始化

    //主動使用和被動使用之間的關聯關係
    
    /*
        對於靜態字段來說,只有直接定義了該字段的類纔會被初始化
        當一個類在初始化時要求其父類全部都已經初始化完畢了
        -XX:+TraceClassLoading  用於追蹤類的加載信息並打印
     */
    public class MyTest1 {
    
        public static void main(String[] args) {
            // 誰定義的靜態變量就表示對誰的主動使用
            System.out.println(MyChild1.str);
            //System.out.println(MyChild1.str2);
        }
    }
    
    class MyParent1 {
        public static  String str = "hello world";
    
        // 靜態代碼塊 初始化執行
        static {
            System.out.println("MyParent1 static block");
        }
    }
    
    class MyChild1 extends  MyParent1 {
    
        public static  String str2 = "hello";
    
        static {
            System.out.println("MyChild1 static block");
        }
    }
    
  1. 所有的java虛擬機實現必須在每個類或接口被java程序 首次主動使用時才初始化他們

  2. 類的加載:指的是將類的.class文件中的二進制數據讀入到內存中,將其放在運行時數據區的方法區,然後在內存中創建一個java.lang.Class對象(規範並未說明Class對象位於哪裏,HotSpot虛擬機將其放在了方法區中)用來封裝類在方法區內的數據結構

    1. 加載.class文件的方式
      • 從本地系統中直接加載
      • 通過網絡下載.class文件
      • 從zip,jar等歸檔文件中加載.class文件
      • 從專有數據庫中提取.class文件
      • 將java源文件動態編譯爲.class文件(動態代理)
  3. JVM參數

    1. 格式: -XX: 開頭
    2. -XX:+<option> 表示開啓option選項如
      • -XX:+TraceClassLoading 用於追蹤類的加載信息並打印
    3. -XX:-<option> 表示關閉option選項
    4. -XX:<option>=<value> 表示將option選擇的值設置爲value
  4. 助記符

    • ldc 表示將int,float或String類型的常量值從常量池推送至棧頂
    • bipush 表示將單字節 (-128 - 127)的常量值推送到棧頂
    • sipush 表示將一個短整型常量值 (-32768 - 32767)推送至棧頂
    • iconst_1 表示將int類型 1 推送至棧頂 (iconst_m1 - iconst_5)
    • anewarray 表示創建一個引用類型的(如類 接口 數組)數組,並將其引用值壓如棧頂
    • newarray 表示創建一個指定的原始類型(int float chart)數組,並將其引用值壓入棧頂
  5. 助記符說明(實例)

    • 助記符這一塊用語言直接去描述還是有點晦澀所以直接通過代碼以及反編譯之後的字節碼更能直觀的說明問題。(javap -c 文件)

      /*
          常量在編譯階段會被存入到調用這個常量的方法所在類的常量池中
          本質上,調用類並沒有直接引用到定義常量的類,因此並不會觸發定義常量的類的初始化
          注意:這裏指的是將常量存放到了MyTest2的常量池中,之後MyTest2與MyParent2就
          沒有任何關係了,甚至可以將MyParent2的class文件刪除
       */
      /*
          助記符:
          ldc 表示將int,float或String類型的常量值從常量池推送至棧頂
          bipush 表示將單字節 (-128 - 127)的常量值推送到棧頂
          sipush 表示將一個短整型常量值 (-32768 - 32767)推送至棧頂
          iconst_1 表示將int類型 1 推送至棧頂 iconst_1 - iconst_5
      
       */
      public class MyTest2 {
          public static void main(String[] args) {
              System.out.println(MyParent2.s);
              System.out.println(MyParent2.str);
              System.out.println(MyParent2.i);
              System.out.println(MyParent2.m);
          }
      }
      class MyParent2 {
          //public static String str = "hello";
          public static final String str = "hello"; // 增加final 關鍵字後 出現了變化
          // 在編譯階段的會被放置在調用這個方法所在的類的常量池中
          public static final short s = 7;
          public static final int i = 128;
          public static final int m = 1;
          static {
              System.out.println("MyParent2 static block");
          }
      }
      
      

      反編譯之後的內容:

      Compiled from "MyTest2.java"
      public class com.lc.jvm.classloader.MyTest2 {
        public com.lc.jvm.classloader.MyTest2();
          Code:
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return
      
        public static void main(java.lang.String[]);
          Code:
             0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
             3: bipush        7
             5: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
             8: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
            11: ldc           #5                  // String hello
            13: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            16: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
            19: sipush        128
            22: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
            25: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
            28: iconst_1
            29: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
            32: return
      }
      
      

四、接口初始化規則與類加載器意義分析

  1. 當一個接口在初始化時,並不要求其父接口都完成了初始化

  2. 只有在真正使用到父接口的時候(如引用接口中所定義的常量時),纔會初始化

  3. 接口中所定義的常量 默認是 public static final

  4. 類加載器 準備階段以及初始化 階段的意義

    public class MyTest6 {
    
        public static void main(String[] args) {
            Signleton signleton = Signleton.getInstance();
            System.out.println("counter1:"+Signleton.counter1);
            System.out.println("counter2:"+Signleton.counter2);
        }
    }
    /*
        分析
        準備階段-> counter1 默認值0 signleton null  counter2 0
        需要注意初始化階段 是按照我們申明的變量 從上往下執行
        counter1 =>1
        signleton=>new Signleton()
        new Signleton()=>執行私有構造方法  counter1++ => 2 ;counter2++;=>1
        System.out.println(counter1);
        System.out.println(counter2);
        counter2 賦值 0
     */
    class Signleton {
        public static int counter1 = 1;
        private static Signleton signleton = new Signleton();
        private Signleton(){
            counter1++;
            counter2++; // 準備階段的重要意義
            System.out.println(counter1);
            System.out.println(counter2);
        }
        public static int counter2 = 0;
    
    
        public static Signleton getInstance(){
            return signleton;
        }
    
    }
    

​ 輸出的結果:

2
1
counter1:2
counter2:0
  1. 類加載流程圖


加載:

過程:

  • 加載:把二進制形式的java類型讀入java虛擬機中

  • 驗證:對文件進行驗證

  • 準備:爲類變量分配內存,設置默認值。但是在到達初始化之前,類變量都沒有初始化爲真正的初始值

  • 解析:解析過程就是類型在常量池中尋找類、接口、字段和方法的符號引用,把這些符號引用替換成直接引用的過程

  • 初始化:爲類變量賦予正確的初始值

  • 類實例化:爲新的對象分配內存;爲實例變量賦予默認值;爲實例變量賦正確的初始值;java編譯器爲它編譯的每一個類都至少生成一個實例初始化方法,在java的class文件中,這個實例初始化方法被稱爲"<init>"。針對源代碼中的每一個類的構造方法,java編譯器都產生了一個<init>方法

  1. 類的加載

    • 類的加載的最終產品是位於內存中的class對象
    • Class對象封裝了類在方法區內的數據結構,並且向java程序員提供了訪問方法區內的數據結構的接口
  2. 類加載器的類型

    • java虛擬機自帶的加載器
      • 根加載器(Bootstrap)
        • 該加載器沒有父類加載器。它負責加載虛擬機的核心類庫,如java.lang.*等。例如從Sample.java可以看出java.lang.Object就是由根類加載器加載的,根類加載器從系統屬性sun.boot.class.path所指定的目錄中加載類庫。根類加載器的實現依賴於底層操作系統,屬於虛擬機實現的一部分,它並沒有繼承java.lang.ClassLoader類
      • 擴展類加載器(Extension)
        • 它的父加載器爲根類加載器。它從java.ext.dirs系統屬性所指定的目錄中加載類庫,或者從JDK安裝的目錄的jre\lib\ext子目錄(擴張目錄)下加載類庫,如果把用戶創建的JAR文件放在這個目錄下,也會自動由擴展類加載器加載。擴展類加載器是純java類 是java.lang.ClassLoader類的子類
      • 系統(應用)類加載器(System)
        • 它的父加載器爲擴展類加載器。它從環境變量classpath或者系統屬性java.lang.path所指定的目錄中加載類,它是用戶自定義加載器的默認父加載器。系統類加載器是純java類,是java.lang.ClassLoader類的子類
    • 用戶自定義的類加載器
      • java.lang.ClassLoader的子類
      • 用戶可以定製類的加載方式(繼承 ClassLoader類)
  3. 類加載器

    • 類加載器用來把類加載到java虛擬機中,從JDK1.2版本開始,類的加載過程採用父親委託機制,這種機制能更好地保證java平臺的安全。在此委託機制中,除了java虛擬機自帶的根類加載器以外,其餘的類加載器都有且只有一個父加載器。當java程序請求加載器loader加載Sample類時,loader首先委託自己的父加載器去加載Sample類,若父加載器能加載,則由父加載器完成加載任務,否則才由加載器loader本身加載Sample類
  4. 類加載器並不需要等到某個類被 首次主動使用 時再加載它

    • JVM規範允許類加載器在預料某個類將要被使用時加預先加載它,如果在預先加載的過程中遇到了 .class 文件缺失或存在錯誤,類加載器必須在 程序首次主動 使用該類時才報告錯誤 (LinkageError錯誤)
    • 如果這個類一直沒有被程序主動使用,那麼 類加載器就不會報告錯誤
  5. 類的驗證

    • 類被加載後,就進入連接階段。連接就是將已經讀入到內存的類的二進制數據合併到虛擬機的運行時環境中去
    • 類的驗證 主要內容(jdk版本不一樣驗證相應也會有區別)
      • 類文件的結構檢查
      • 語義檢查
      • 字節碼驗證
      • 二進制兼容性的驗證
      • 魔數驗證
  6. 類的準備

    • 在準備階段java虛擬機爲類的靜態變量分配內存,並設置默認初始值。例如 對於以下 Sample類,在準備階段,將爲int類型的靜態變量a分配4個字節的內存空間,並且賦予默認值0,爲long類型的靜態變量b分配8個字節的內存空間,並且賦予默認值0
    public class Sample{
         private static int a = 1;
         private static long b;
         static {
          b = 2;
        }
         ...
    }
    
    • 在初始化階段,java虛擬機執行類的初始化語句,爲類的靜態變量賦予初始值。在程序中,靜態變量的初始化有兩種途徑:1. 在靜態變量的聲明處進行初始化 2. 在靜態代碼塊中進行初始化。例如在以下代碼中,靜態變量a和b都是顯示的初始化,而靜態變量c沒有被顯示的初始化,它將保持默認值0
    public class Sample{
         private static int a = 1; //在靜態變量聲明處進行初始化
         private static long b;
         private static long c;
         static {
          b = 2; // 在靜態代碼塊中進行初始化
        }
         ...
    }
    
    • 靜態變量的聲明語句,以及靜態代碼塊都被看做類的初始化語句,java虛擬機會按照初始化語句在類文件中的先後順序依次執行他們。例如當一下Sample類被初始化後,它的靜態變量a的取值爲4
    public class Sample{
      static int a = 1;
      static {
        a = 2;
      }
      static {
        a = 4;
      }
      public static void main(String[] args){
        System.out.println("a="+a); //輸出 a=4
      }
    }
    
  7. 類的初始化

    • 類的初始化步驟
      • 假如這個類還沒有被加載和連接,那就先進行加載和連接
      • 假如類存在直接父類,並且這個父類還沒有被初始化,那就先初始化直接父類
      • 假如類中存在初始化語句,那就依次執行這些初始化語句
  8. 類的初始化時機

    • 創建類的實例(new 一個實例)

    • 訪問某個類或接口的靜態變量,或者對該靜態變量賦值(對應到助記符 getstatic putstatic)

    • 調用類的靜態方法(對應到助記符 invokestatic)

    • 反射(如Class.forName("com.test.Test") )

    • 初始化一個類的子類

    • java虛擬機啓動時被標明爲啓動類的類(java Test)

    • JDK1.7開始提供的動態語言支持:java.lang.invoke.MethodHandle實例的解析結果 REF_getStatic REF_putStatic REF_invokeStatic 句柄對應的類沒有初始化則初始化

    • 注意: 除了上面的七種情形,其他使用java類的方式都被看做是被動使用,不會導致類的初始化。

    • 當java虛擬機初始化一個類時,要求它的所有父類都已經被初始化,但是這條規則並不適用於接口。

      • 在初始化一個類時,並不會先初始化它所有實現的接口

      • 在初始化一個接口時,並不會先初始化它的父接口

      • 因此,一個父接口並不會因爲它的子接口或者實現類的初始化而初始化。只有當程序首次使用特定接口的靜態變量時,纔會導致該接口的初始化。

      • 通過代碼方式證明上述的結論

        /*
            當一個接口在初始化時,並不要求其父接口都完成了初始化
             只有在真正使用到父接口的時候(如引用接口中所定義的常量時),纔會初始化
         */
        public class MyTest7 {
            public static void main(String[] args) {
                // System.out.println(MyChild7.b ); // 在初始化一個類時,並不會先初始化它所有實現的接口
                System.out.println(IMyChild7.thread);  // 驗證 在初始化一個接口時,並不會先初始化它的父接口
            }
        }
        
        interface MyParent7 {
           public static Thread thread = new Thread(){
               // 這是實例代碼塊 在對象實例化時 執行。和靜態代碼塊有很大區別 靜態代碼塊只執行一次
                {
                    System.out.println("MyParent7 invoke");
                }
            };
        }
        class MyChild7  implements  MyParent7{
            public static    int b = new Random().nextInt(3);
        }
        interface IMyChild7 extends MyParent7 {
            public static Thread thread = new Thread(){
                {
                    System.out.println("IMyChild7 invoke");
                }
            };
        }
        
        
  • 只有當程序訪問的靜態變量或靜態方法確實在當前類或當前接口中定義時,纔可以認爲是對類或接口的主動使用

  • 通過子類去調用父類的靜態變量或者父類的靜態方法本質上都是對於父類的主動使用,而不是對於自己的主動使用

    /*
        通過子類去調用父類的靜態變量或者父類的靜態方法本質上都是對於父類的主動使用
        而不是對於自己的主動使用。
     */
    public class MyTest11 {
        public static void main(String[] args) {
            System.out.println(Child11.a);
        }
    }
    class Parent11{
        static  int a = 1;
        static {
            System.out.println("Parenr11 static block");
        }
    
        static void doSomething(){
            System.out.println("do something");
        }
    }
    class Child11 extends  Parent11{
        static int b = 2;
        static {
            System.out.println("Child11 static block");
        }
    }
    
    /*
       輸出:
       Parenr11 static block
       1
    */
    
  • 調用ClassLoader類的loadClass方法加載一個類,並不是對類的主動使用,不會導致類的初始化 (閱讀 ClassLoader javadoc文檔)

     public class MyTest12 {
        public static void main(String[] args) throws Exception {
            ClassLoader classLoader = ClassLoader.getSystemClassLoader();
            Class<?> aClass = classLoader.loadClass("com.lc.jvm.classloader.CL");
            System.out.println(aClass);
            System.out.println("--------");
            Class<?> aClass1 = Class.forName("com.lc.jvm.classloader.CL");//通過反射 會導致類的初始化
            System.out.println(aClass1);
        }
    }
    class  CL {
        static {
            System.out.println("Class CL");
        }
    }
    /*
    輸出:
    class com.lc.jvm.classloader.CL
    --------
    Class CL
    class com.lc.jvm.classloader.CL
    */
    
  1. 類加載器的雙親委託機制

    • 在雙親委託機制中,各個加載器按照父子關係形成 樹形結構,除了根類加載器之外,其餘的類加載器都有且只有一個父加載器
    • 若有一個類加載器能夠成功加載Test類,那麼這個類加載器被稱爲 定義類加載器,所有能成功返回Class對象引用的類加載器(包括定義類加載器)都被稱爲 初始類加載器
  2. 類加載器的層次關係

    public class MyTest14 {
    
        public static void main(String[] args) throws  Exception{
            // 獲取當前線程的上下文類加載器
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            // 定義資源
            String resoureName = "com/lc/jvm/classloader/MyTest13.class";
            // 獲取資源
            Enumeration<URL> resources = classLoader.getResources(resoureName);
            while (resources.hasMoreElements()) {
                URL url = resources.nextElement();
                System.out.println(url);
            }
        }
    }
    
    1. 獲取ClassLoader的途徑

      • 獲取當前類的ClassLoader : clazz.getClassLoader();
      • 獲取當前線程上下文的ClassLoader : Thread.currentThread().getContextClassLoader();
      • 獲取系統的ClassLoader: ClassLoader.getSystemClassLoader();
      • 獲取調用者的ClassLoader: DriverManager.getCallerClassLoader();
    2. ClassLoader javadoc文檔

      • A class loader is an object that is responsible for loading classes. The class ClassLoader is an abstract class. Given the binary name of a class, a class loader should attempt to locate or generate data that constitutes a definition for the class. A typical strategy is to transform the name into a file name and then read a "class file" of that name from a file system.
        Every Class object contains a reference to the ClassLoader that defined it.
        Class objects for array classes are not created by class loaders, but are created automatically as required by the Java runtime. The class loader for an array class, as returned by Class.getClassLoader() is the same as the class loader for its element type; if the element type is a primitive type, then the array class has no class loader.
        Applications implement subclasses of ClassLoader in order to extend the manner in which the Java virtual machine dynamically loads classes.
        Class loaders may typically be used by security managers to indicate security domains.
        The ClassLoader class uses a delegation model to search for classes and resources. Each instance of ClassLoader has an associated parent class loader. When requested to find a class or resource, a ClassLoader instance will delegate the search for the class or resource to its parent class loader before attempting to find the class or resource itself. The virtual machine's built-in class loader, called the "bootstrap class loader", does not itself have a parent but may serve as the parent of a ClassLoader instance.
        Class loaders that support concurrent loading of classes are known as parallel capable class loaders and are required to register themselves at their class initialization time by invoking the ClassLoader.registerAsParallelCapable method. Note that the ClassLoader class is registered as parallel capable by default. However, its subclasses still need to register themselves if they are parallel capable. In environments in which the delegation model is not strictly hierarchical, class loaders need to be parallel capable, otherwise class loading can lead to deadlocks because the loader lock is held for the duration of the class loading process (see loadClass methods).
        Normally, the Java virtual machine loads classes from the local file system in a platform-dependent manner. For example, on UNIX systems, the virtual machine loads classes from the directory defined by the CLASSPATH environment variable.
        However, some classes may not originate from a file; they may originate from other sources, such as the network, or they could be constructed by an application. The method defineClass converts an array of bytes into an instance of class Class. Instances of this newly defined class can be created using Class.newInstance.
        The methods and constructors of objects created by a class loader may reference other classes. To determine the class(es) referred to, the Java virtual machine invokes the loadClass method of the class loader that originally created the class

      • 對應說明:類加載器是負責加載類的對象。 ClassLoader類是一個抽象類。給定類的二進制名稱,類加載器應嘗試定位或生成構成類定義的數據。典型的策略是將名稱轉換爲文件名,然後從文件系統中讀取該名稱的“類文件”。
        每個Class對象都包含對定義它的ClassLoader的引用。
        數組類的類對象不是由類加載器創建的,而是根據Java運行時的需要自動創建的。 Class.getClassLoader()返回的數組類的類加載器與其元素類型的類加載器相同;如果元素類型是基本類型,則數組類沒有類加載器。
        應用程序實現ClassLoader的子類,以便擴展Java虛擬機動態加載類的方式。
        安全管理器通常可以使用類加載器來指示安全域。
        ClassLoader類使用委派模型來搜索類和資源。 ClassLoader的每個實例都有一個關聯的父類加載器。當請求查找類或資源時,ClassLoader實例會在嘗試查找類或資源本身之前,將對類或資源的搜索委託給其父類加載器。虛擬機的內置類加載器(稱爲“引導類加載器”)本身不具有父級,但可以作爲ClassLoader實例的父級。
        支持併發加載類的類加載器稱爲並行加載類加載器,需要通過調用ClassLoader.registerAsParallelCapable方法在類初始化時註冊自己。請注意,ClassLoader類默認註冊爲並行。但是,如果它們具有並行能力,它的子類仍然需要註冊自己。在委託模型不是嚴格分層的環境中,類加載器需要具有並行能力,否則類加載會導致死鎖,因爲加載器鎖在類加載過程的持續時間內保持(請參閱loadClass方法)。
        通常,Java虛擬機以與平臺相關的方式從本地文件系統加載類。例如,在UNIX系統上,虛擬機從CLASSPATH環境變量定義的目錄中加載類。
        但是,某些類可能不是源自文件;它們可能來自其他來源,例如網絡,或者它們可以由應用程序構建。方法defineClass將字節數組轉換爲類Class的實例。可以使用Class.newInstance創建此新定義的類的實例。
        由類加載器創建的對象的方法和構造函數可以引用其他類。要確定所引用的類,Java虛擬機將調用最初創建該類的類加載器的loadClass方法

      • 定義自己的類加載器

        /*
            自定義類加載器
         */
        public class MyTest16 extends  ClassLoader{
            // 定義classLoaderName
            private String classLoaderName;
            // 定義文件後綴
            private final String fileExtension = ".class";
        
            public MyTest16(String classLoaderName){
                super();// 將系統類加載器當做該類加載器的父加載器
                this.classLoaderName = classLoaderName;
            }
        
            public MyTest16(ClassLoader parent,String classLoaderName){
                super(parent);// 顯示指定該類加載器的父類加載器
                this.classLoaderName = classLoaderName;
            }
        
            @Override
            public String toString() {
                return "["+this.classLoaderName+"]";
            }
        
            @Override
            protected Class<?> findClass(String name) throws ClassNotFoundException {
                byte[] bytes = loadClassData(name);
                return this.defineClass(name,bytes,0,bytes.length);
            }
        
            private byte[] loadClassData(String name){
                InputStream is = null;
                byte[] bytes = null;
                ByteArrayOutputStream baos = null;
        
                try{
                    //this.classLoaderName = this.classLoaderName.replace(".","/");
                    name = name.replace(".","/");
                    is = new FileInputStream(new File(name+this.fileExtension));
                    baos = new ByteArrayOutputStream();
                    int ch = 0;
                    while (-1 != (ch = is.read())){
                        baos.write(ch);
                    }
                    bytes = baos.toByteArray();
                }catch (Exception e) {
                    e.printStackTrace();
                }finally {
                    try{
                        is.close();
                        baos.close();
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
                return bytes;
            }
        
            public static void main(String[] args) throws Exception {
                MyTest16 load = new MyTest16(new MyTest16("parent"),"load1");
                test(load);
            }
        
            public static void test(ClassLoader classLoader) throws Exception{
                Class<?> aClass = classLoader.loadClass("com.lc.jvm.classloader.MyTest1");
                Object o = aClass.newInstance();
                System.out.println(o);
                System.out.println(aClass.getClassLoader());
            }
        }
        
    3. 命名空間

      • 每個類加載器都有自己的命名空間,命名空間由該加載器及所有父加載器所加載的類組成
      • 在同一個命名空間中,不會出現類的完整名字(包括類的包名)相同的兩個類
      • 在不同的命名空間,有可能出現類的完整名字(包括類的包名)相同的兩個類
    4. 類的卸載

      • 當MySample類被加載、連接和初始化後,它的生命週期就開始了。當代表MySample類的Class對象不再引用,即不可觸及時,Class對象就會結束生命週期,MySample類在方法區內的數據也會被卸載。從而結束MySample類的生命週期
      • 一個類何時結束生命週期,取決於代表它的Class對象何時結束生命週期
      • 由java虛擬機自帶的類加載器所加載的類,在虛擬機的生命週期中,始終不會被卸載。前面已經介紹過,java虛擬機自帶的類加載器包括根類加載器、擴展類加載器、系統類加載器。java虛擬機本省會始終引用這些類加載器,而這些類加載器則會始終引用他們所加載的類的Class對象,因此這些Class對象始終是可觸及的。
      • 由用戶自定義的類加載器所加載的類可以被卸載
      • 說明:運行以上程序時,MySample類有loader加載,在類加載器的內部實現中,用一個java集合來存放所加載類的引用。另一方面,一個Class對象總會引用它的類加載器,調用Class對象的getClassLoader()方法,就能獲取到它的類加載器。由此可見,代表Sample類的Class實例與Loader之間爲雙向關聯關係。一個類的實例總是引用代表這個類的Class對象,在Object類中定義了getClass() 方法,這個方法返回代表對象所屬類的Class對象的引用,此外,所有的java類都有一個靜態屬性class,它引用代表這個類的Class對象

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章