Class.forName和classloader的區別分析

class.forName()除了將類的.class文件加載到jvm中之外,還會對類進行解釋,執行類中的static塊。
classLoader只幹一件事情,就是將.class文件加載到jvm中,不會執行static中的內容,只有在newInstance纔會去執行static塊。

Class.forName(name, initialize, loader)帶參函數也可控制是否加載static塊。並且只有調用了newInstance()方法採用調用構造函數,創建類的對象。

先了解下類加載機制,

5個過程。
a.加載:java類運行時候會生成一個class字節碼文件,加載的過程就是去我們的操作系統尋找這個class文件。而獲取.class文件的方式,可以通過jar包、war包、網絡中獲取、JSP文件生成等方式。

b.鏈接:又分爲3個小過程。
    1.驗證:驗證被加載後的類是否有正確的結構,類數據是否會符合虛擬機的要求,確保不會危害虛擬機安全。
    2.準備:爲類的靜態變量(static filed)在方法區分配內存,並賦默認初值(0值或null值)。如static int maxNum = 10;
靜態變量maxNum 就會在準備階段被賦默認值0;而對於一般的成員變量是在類實例化時候,隨對象一起分配在堆內存中。靜態常量(static final filed)會在準備階段賦程序設定的初值,如static final int maxNum  = 50; 靜態常量a就會在準備階段被直接賦值爲50,對於靜態變量,這個操作是在初始化階段進行的。
    3.解析:解析是將符號引用替換爲直接引用,解析動作針對類或接口,字段,類或接口的方法進行解析。
首先是用類加載器加載這個類。在加載的過程中逐步解析類中的字段和方法。
符號引用是以字面量的實形式明確定義在常量池中,直接引用是指向目標的指針,或者相對偏移量。

c.初始化:類初始化是類加載的最後一步,除了加載階段,用戶可以通過自定義的類加載器參與,其他階段都完全由虛擬機主導和控制。主要工作是爲靜態變量賦程序設定的初值。如static int maxNum  = 10;在準備階段,a被賦默認值0,在初始化階段就會被賦值爲10。
當初始化一個類的時候,如果發現其父類沒有進行過初始化,則首先觸發父類初始化。

d.使用:在需要使用的地方調用

e.卸載:使用完了,java虛擬機進行清理。

類的加載的時機,有以下5中情況
1  遇到new、getstatic、putstatic或invokestatic這四條字節碼指令
2  使用java.lang.reflect包的方法對類進行反射調用的時候,如果類沒有進行過初始化,則需要先觸發其初始化。
3 當初始化一個類的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類的初始化
4 當虛擬機啓動時,用戶需要指定一個要執行的主類(包含main()方法的那個類),虛擬機會先初始化這個主類。
5當使用jdk1.7動態語言支持時,如果一個java.lang.invoke.MethodHandle實例最後的解析結果 REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,並且這個方法句柄所對應的類沒有進行過初始化,則會先觸發其初始化。

上面介紹了類加載的幾個過程,再具體看下Class.forName和classloader的區別。用代碼演示。
先定義一個類

package com.test.a;

public class User {

	private static int a = 5;
	private static int b;
	private static int c = testC();
	
	static {
		System.out.println("執行靜態代碼塊");
		System.out.println("執行靜態變量a="+a);
	}
	
	public static void testA() {
		System.out.println("執行靜態代方法testA");
	}
	
	public static void testB() {
		b = 10;
		System.out.println("輸出靜態變量b="+b);
		System.out.println("執行靜態代方法testB");
	}
	
	public static int testC() {
		System.out.println("執行靜態代方法testC");
		return 20;
	}
	
	
}

分別使用Class.forName和classloader加載,查看結果

package com.test.a;

public class TestClassLoad {

	public static void main(String[] args) {
		try {
			//Class.forName加載
			String userPath = "com.test.a.User";
			Class<?> forName = Class.forName(userPath);
			System.out.println("-------------------forName加載--------------------"+forName);
			//classLoader加載
			ClassLoader classLoader = ClassLoader.getSystemClassLoader();
			classLoader.loadClass(userPath);
			System.out.println("-------------------classLoader加載--------------------");
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}



可以看到classloader只是做了加載,沒有對類初始化,而Class.forName加載了靜態變量和靜態代碼塊,但是並沒有加載形態方法。只有在靜態變量調用了靜態方法賦值的情況下才會加載靜態方法。

看下Class.forName源碼分析,他實際上也是調用了classloader去加載,但因爲他的第二個參數指定了true,所以會去初始化類

要想不初始化,調用他的重載方法Class.forName(userPath,false,ClassLoader.getSystemClassLoader());將第二個參數設置爲false,指Class被加載後是不是必須被初始化。
 

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