淺析類裝載

要使用類,必須有兩個步驟,一個是加載類,而後是初始化。java

建立一個類的實例可使用到的方法:網絡

  • 使用new關鍵字
  • 經過反射
  • 克隆
  • 反序列化

在什麼狀況下使用類,不須要初始化類函數

經過子類調用父類的靜態變量,子類不會被初始化,可是子類會被加載。ui

public class Parent {
    static {
        System.out.println("Parent init");
    }
    public static int v = 100;
}
public class Child extends Parent {
    static {
        System.out.println("Child init");
    }
}
public class UseParent {
    public static void main(String[] args) {
        System.out.println(Child.v);
    }
}

經過JVM參數-XX:+TraceClassLoading運行獲得如下結果spa

[Loaded com.guanjian.Parent from file:/E:/classload/out/production/classload/]
[Loaded com.guanjian.Child from file:/E:/classload/out/production/classload/]
Parent init
100.net

由結果可知,只有父類被初始化了,子類沒有被初始化,可是被加載了。日誌

直接調用某個類的final修飾的靜態變量,不但不會初始化該類,連加載都不會。code

public class FinalFieldClass {
    public static final String constString = "CONST";
    static {
        System.out.println("FinalFieldClass init");
    }
}
public class UseFinalField {
    public static void main(String[] args) {
        System.out.println(FinalFieldClass.constString);
    }
}

經過JVM參數-XX:+TraceClassLoading運行獲得如下結果orm

[Loaded java.net.URI$Parser from C:\Program Files\Java\jdk1.8.0_91\jre\lib\rt.jar]
[Loaded java.lang.Class$MethodArray from C:\Program Files\Java\jdk1.8.0_91\jre\lib\rt.jar]
[Loaded java.lang.Void from C:\Program Files\Java\jdk1.8.0_91\jre\lib\rt.jar]
CONST繼承

由結果可知,不但沒有打印FinalFieldClass init,連Loaded FinalFieldClass的日誌都沒有。這是由於final常量直接存放到了常量池中,所以FinalFieldClass類不會被加載。

加載類的步驟

咱們都知道,java在編譯類後並非產生固有機器的機器碼,而是一段字節碼,這段字節碼能夠存放於任何地方,如.class文件,jar包中,能夠經過網絡傳輸。JVM虛擬機在拿取到這段二進制數據流字節碼後,就會處理這些數據,並最終轉換成一個java.lang.Class的實例(注意,這裏並非類自己的實例)。java.lang.Class實例是訪問類型元數據的接口,也是實現反射的關鍵數據。經過Class類提供的接口,能夠訪問一個類型的方法,字段等信息。

public class ClassTest {
    public static void main(String[] args) throws ClassNotFoundException {
        Class clzStr = Class.forName("java.lang.String");
        //獲取該類的全部方法
        Method[] ms = clzStr.getDeclaredMethods();
        for (Method m:ms) {
            //獲取該方法的修飾符
            String mod = Modifier.toString(m.getModifiers());
            //打印方法的修飾符,方法名跟起始括號
            System.out.print(mod + " " + m.getName() + " (");
            //獲取方法的全部參數類型
            Class<?>[] ps = m.getParameterTypes();
            //若是沒有參數,直接打印結束括號
            if (ps.length == 0) {
                System.out.print(')');
            } else {
                //取出全部的參數類型名稱,以逗號分隔
                for (int i = 0; i < ps.length; i++) {
                    char end = i == ps.length - 1 ? ')' : ',';
                    System.out.print(ps[i].getSimpleName() + end);
                }
            }
            System.out.println();
        }
    }
}

運行結果:

public equals (Object)
public toString ()
public hashCode ()
public compareTo (String)
public volatile compareTo (Object)
public indexOf (String,int)
public indexOf (String)
public indexOf (int,int)
public indexOf (int)
static indexOf (char[],int,int,char[],int,int,int)
static indexOf (char[],int,int,String,int)
public static valueOf (int)
public static valueOf (long)
public static valueOf (float)
public static valueOf (boolean)
public static valueOf (char[])
public static valueOf (char[],int,int)
public static valueOf (Object)
public static valueOf (char)
public static valueOf (double)
public charAt (int)
private static checkBounds (byte[],int,int)
public codePointAt (int)
public codePointBefore (int)
public codePointCount (int,int)
public compareToIgnoreCase (String)
public concat (String)
public contains (CharSequence)
public contentEquals (CharSequence)
public contentEquals (StringBuffer)
public static copyValueOf (char[])
public static copyValueOf (char[],int,int)
public endsWith (String)
public equalsIgnoreCase (String)
public static transient format (Locale,String,Object[])
public static transient format (String,Object[])
public getBytes (int,int,byte[],int)
public getBytes (Charset)
public getBytes (String)
public getBytes ()
public getChars (int,int,char[],int)
 getChars (char[],int)
private indexOfSupplementary (int,int)
public native intern ()
public isEmpty ()
public static transient join (CharSequence,CharSequence[])
public static join (CharSequence,Iterable)
public lastIndexOf (int)
public lastIndexOf (String)
static lastIndexOf (char[],int,int,String,int)
public lastIndexOf (String,int)
public lastIndexOf (int,int)
static lastIndexOf (char[],int,int,char[],int,int,int)
private lastIndexOfSupplementary (int,int)
public length ()
public matches (String)
private nonSyncContentEquals (AbstractStringBuilder)
public offsetByCodePoints (int,int)
public regionMatches (int,String,int,int)
public regionMatches (boolean,int,String,int,int)
public replace (char,char)
public replace (CharSequence,CharSequence)
public replaceAll (String,String)
public replaceFirst (String,String)
public split (String)
public split (String,int)
public startsWith (String,int)
public startsWith (String)
public subSequence (int,int)
public substring (int)
public substring (int,int)
public toCharArray ()
public toLowerCase (Locale)
public toLowerCase ()
public toUpperCase ()
public toUpperCase (Locale)
public trim ()

Class.forName能夠獲得表明String類的Class實例,它是反射中最重要的方法。

二進制數據流字節碼被加載到虛擬機以後,會進行一系列的驗證檢查,主要步驟以下。

  • 格式檢查:包括魔數檢查(識別文件類型開頭的幾個十六進制數,每一種文件類型都不一樣),版本檢查,長度檢查
  • 語義檢查:是否繼承final,是否有父類,抽象方法是否有實現
  • 字節碼驗證:跳轉指令是否指向正確位置,操做數類型是否合理
  • 符號引用驗證:符號引用的直接引用是否存在

以上比較複雜,暫略過。

當一個類驗證經過時,虛擬機就會進入準備階段。分配內存空間,分配初始值。若是類存在常量字段,若是被final修飾,就會被直接放入常量池中。若是沒有final修飾,就會在初始化中賦值,而不是直接放入常量池。

準備階段完成後,就是解析類,解析類就是把字節碼中的類,接口,字段,方法放入JVM虛擬機實際內存的地址中,方便程序能夠真正執行,好比說類的方法會有一個方法表,當須要調用一個類的方法時,就要知道這個方法在方法表中的偏移量,直接調用該方法。

類的初始化是類裝載的最後一個階段。初始化的重要工做就是執行類的初始化方法<clinit>。方法<clinit>是由編譯器自動生成的,它是由類靜態成員的賦值語句以及static語句合併產生的。相似代碼以下

public class SimpleStatic {
    public static int id = 1;
    public static int number;
    static {
        number = 4;
    }
}

在<clinit>函數中,前後對id和number兩個成員變量進行賦值。但若是成員變量是final修飾的,則不在此階段賦值,而是在準備階段賦值的。

相關文章
相關標籤/搜索