面試準備之類的加載過程

目錄

一.類的加載順序

二.類的加載過程

三.類加載機制


類的加載過程,這是一個經常會被問到的面試題,11月11號去一家公司做了一套筆試題,裏面就有讓你寫出打印順序的。

一.類的加載順序

1.父類靜態變量
2.父類靜態代碼塊
3.子類靜態變量
4.子類靜態代碼塊
5.父類變量
6.父類非靜態代碼塊
7.父類構造方法
8.子類變量
9.子類非靜態代碼塊
10.子類構造方法

/**
 * @Author wangbiao
 * @Date 2019-11-23 22:12
 * @Decripition TODO
 **/
public class ClassLoadTest {
    public static void main(String[] args) {
        Son son = new Son();
    }
}


class Parent{
    private static final String first="first";
    private static String second = "second";
    private String third = "third";

    static {
        System.out.println("【父類】靜態代碼塊:first:"+first+";second:"+second);
    }
    {
        System.out.println("【父類】非靜態代碼塊:first:"+first+";second:"+second+"third:"+third);
    }

    public Parent(){
        System.out.println("【父類】構造方法:first:"+first+";second:"+second+"third:"+third);

    }

}


class Son extends Parent{
    private static final String fourth="fourth";
    private static String fifth = "fifth";
    private String sixth = "sixth";

    static {
        System.out.println("【子類】靜態代碼塊:fourth:"+fourth+";fifth:"+fifth);
    }
    {
        System.out.println("【子類】非靜態代碼塊:fourth:"+fourth+";fifth:"+fifth+"sixth:"+sixth);
    }

    public Son(){
        System.out.println("【子類】構造方法:fourth:"+fourth+";fifth:"+fifth+"sixth:"+sixth);

    }

}

運行結果:

父類】靜態代碼塊:first:first;second:second
【子類】靜態代碼塊:fourth:fourth;fifth:fifth
【父類】非靜態代碼塊:first:first;second:secondthird:third
【父類】構造方法:first:first;second:secondthird:third
【子類】非靜態代碼塊:fourth:fourth;fifth:fifthsixth:sixth
【子類】構造方法:fourth:fourth;fifth:fifthsixth:sixth

二.類的加載過程

類的加載全過程:1.加載  2.準備 3.驗證 4.解析 5.初始化

1.加載:通過類的全限定名稱找到要加載的二進制字節流文件加載到內存中,將靜態存儲結構轉變成方法區的運行時數據結構,並在堆內存裏面創建一個class對象作爲訪問方法區類信息的入口。


2.驗證:驗證加載進來的數據是否符合虛擬機的規範,並且不會危害j虛擬機的安全。驗證包括文件格式驗證,元數據驗證,字節碼驗證碼,符號引用驗證。

文件格式驗證:驗證加載進來的數據是否符合class文件的規範,只有符合規範了,數據才能被存儲到方法區。
在這個階段做的比如final方法是否被重寫,final類是否被繼承。

3.準備:給靜態變量分配內存空間,這個內存空間是在方法區中的,並且會給默認值,默認值一般是0,如果類有靜態常量的話就會直接賦初值。例如下面的代碼,其中first是靜態常量,就會直接將123賦值給它,而second是靜態變量,就會賦值0。比如8種基本類型的初值,默認爲0;引用類型的初值則爲null。

private static final int first= 123;
private static int second = 456;

4.解析:將常量池裏面的符號引用轉變成直接引用。

5.初始化:執行類構造器,類構造器<clinit>()方法是由編譯器自動收集類中的所有類變量的賦值動作和靜態語句塊(static塊)中的語句合併產生的。即執行給類變量賦值和執行靜態代碼塊裏面的內容,如果這個類繼承了父類,那麼先執行父類的類變量賦值和靜態代碼塊的內容。所以這裏就和前面代碼執行的輸出過程相驗證。先是執行類靜態變量賦值,再執行靜態代碼塊,最後執行構造方法。

如果這裏是 Student student = new Student()的話,後面還有幾步操作

5.在堆中分配內存空間:分配內存空間包括父類和子類的所有實例變量,但不包括靜態變量。

6.對實例變量賦值:將方法區裏面的實例變量拷貝一份到堆中,賦默認值,應該賦初值的。

7.執行非靜態代碼塊和構造方法,如果有父類,先執行弗雷德非靜態代碼塊和構造方法,然後在棧區定義引用變量student,並將實例在堆內存地址賦值給student。

到此類就被完全加載進來了。Student student = new Student(),這裏還涉及到一個知識點,就是單例的雙重檢測,爲什麼在寫單例的雙重檢測的時候,申明的類變量要用volatitle修飾,可以看到Student student = new Student()是經過三步走的:

A.在堆內存開啓存儲空間   B.實例話對象    C.賦值給引用變量
這裏如果不用volatitle修飾的話,由於指令會重排序,可能會先執行A,再執行C,最後執行B,這樣就會在多線程下,線程一執行到了C,然後線程二去獲取對象,發現對象非空,就拿到了單例,其實線程一還沒有執行B,線程二拿到的是一個不完整的對象,這就有可能出問題,volatitle修飾的話就不會出現指令重排了。

三.類加載機制

1.類加載器分類
     

根類加載器 bootstrap class loader 
它用來加載 Java 的核心庫(JAVA_HOME/jre/lib/rt.jar,sun.boot.class.path路徑下的內容),是用原生代碼(C語言)來實現的,並不繼承自 java.lang.ClassLoader。

加載擴展類和應用程序類加載器。並指定他們的父類加載器。

擴展類加載器 extensions class loader

用來加載 Java 的擴展庫(JAVA_HOME/jre/ext/*.jar,或java.ext.dirs路徑下的內容) 。Java 虛擬機的實現會提供一個擴展庫目錄。該類加載器在此目錄裏面查找並加載 Java類。

應用類加載  application class loader

它根據 Java 應用的類路徑(classpath,java.class.path 路徑下的內容)來加載 Java 類。一般來說,Java 應用的類都是由它來完成加載的。

自定義類加載器

可以自己寫一個類加載器,不過這個類加載器要繼承ClassLoader這個類

2.雙親委派機制

雙親委派機制是當一個類加載器接收到類加載的請求的時候自己首先不去加載,而是交給父類去加載,父類也不去加載,最後都會交給根加載器加載,如果根加載器在發現掃描的範圍內沒有這個類的時候,就交給父類加載,父類也沒有加載成功的話,就交給子類加載,如果子類還是加載不成功,那就會報ClassNotFoundException。

雙親委派機制的好處就是在全局能確定一個唯一類,在虛擬機中類的確定不僅和類的全限定名稱有關,還和類加載器有關,如果不用雙親委派機制的話,一個類有可能被多個類加載器加載,然後出現賦值的時候問題,不能確定是哪一個類了,雙親委派模型可以確定一個類最終都是同一個類加載器加載,確保加載結果相同。這也是保證java核心庫的安全,如果用戶寫了一個java.lang.Object類的話,這個類也會最終交由根加載器加載,但是在根加載器加載的時候會先去加載核心類庫裏面的類,發現核心類庫裏面已經有了就不會再加載這個了。

四.tomcat的類加載機制

tomcat的類加載機制首先嚐試去加載某個類,如果找不到再代理給父類加載器。這與一般類加載器的順序是相反的

CommonClassLoader、CatalinaClassLoader、SharedClassLoader和WebappClassLoader則是Tomcat自己定義的類加載器,它們分別加載/common/*/server/*/shared/*(在tomcat 6之後已經合併到根目錄下的lib目錄下)和/WebApp/WEB-INF/*中的Java類庫。其中WebApp類加載器和Jsp類加載器通常會存在多個實例,每一個Web應用程序對應一個WebApp類加載器,每一個JSP文件對應一個Jsp類加載器。

commonLoader:Tomcat最基本的類加載器,加載路徑中的class可以被Tomcat容器本身以及各個Webapp訪問;
catalinaLoader:Tomcat容器私有的類加載器,加載路徑中的class對於Webapp不可見;
sharedLoader:各個Webapp共享的類加載器,加載路徑中的class對於所有Webapp可見,但是對於Tomcat容器不可見;
WebappClassLoader:各個Webapp私有的類加載器,加載路徑中的class只對當前Webapp可見;

CommonClassLoader能加載的類都可以被Catalina ClassLoader和SharedClassLoader使用,從而實現了公有類庫的共用,而CatalinaClassLoader和Shared ClassLoader自己能加載的類則與對方相互隔離。

WebAppClassLoader可以使用SharedClassLoader加載到的類,但各個WebAppClassLoader實例之間相互隔離。

而JasperLoader的加載範圍僅僅是這個JSP文件所編譯出來的那一個.Class文件,它出現的目的就是爲了被丟棄:當Web容器檢測到JSP文件被修改時,會替換掉目前的JasperLoader的實例,並通過再建立一個新的Jsp類加載器來實現JSP文件的HotSwap功能

tomcat類加載的設計模式沒有使用雙親委派模型,而是採用了隔離的方式,每一個webappClassLoader都會去加載自己目錄下面的class,不傳遞給父類去加載,那爲什麼要這麼設計呢?

這是由於一個tomcat裏面可以加載多個工程,每一個工程都需要加載自己的類庫和自己下面的類,如果兩個工程裏面引用的類庫一樣,但版本不一樣,由於都交給頂層類加載器加載的話,這樣就會很容易出現問題。

web容器也有自己依賴的類庫,不能於應用程序的類庫混淆。基於安全考慮,應該讓容器的類庫和程序的類庫隔離開來。

 

五.上下文類加載器

這個太難理解,放到後面去理解

https://blog.csdn.net/yangcheng33/article/details/52631940

六.classLoader

Class.forName加載類時將類進了初始化,而ClassLoader的loadClass並沒有對類進行初始化,只是把類加載到了虛擬機中。參考

在我們熟悉的Spring框架中的IOC的實現就是使用的ClassLoader。

而在我們使用JDBC時通常是使用Class.forName()方法來加載數據庫連接驅動。這是因爲在JDBC規範中明確要求Driver(數據庫驅動)類必須向DriverManager註冊自己。

文章:https://www.jianshu.com/p/dd39654231e0

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