從對象到類,Java中須要知道的這些東西

1. 對象的誕生

  在平時的開發中,咱們使用對象的時候,都是直接new一個臨時變量而後進行各類邏輯賦值而後返回,可是你有沒有想過一個對象在建立的過程當中經歷了什麼呢,爲何建立時靜態變量就已經賦完值了?這些彷佛理所固然的操做其實裏邊仍是有點東西的。java

  先說下一個對象誕生時的整個過程,一個對象的誕生必定會通過加載類的信息—>爲即將誕生的對象分配內存空間—>將對象的成員變量賦上一個默認值—>捏臉(在頭部設置對象的類信息和GC年齡)—>將對象的成員變量初始化爲代碼中寫的值這五個流程,各個流程都有其重要的做用。面試

  1. 類加載:在第一次使用該對象的時候會進行類的加載工做,以後便再也不加載。
  2. 分配空間JVM給對象在堆上分配使用的空間,有兩種方式。(題外話:對象不必定在堆上建立,感興趣的話能夠搜一下爲何)
    1. 指針碰撞:就是有序分配。維護一個指針,表示當前內存空閒的地址,下次分配空間的時候從這個位置開始,結束後更新指針位置。
    2. 空閒列表:就是隨便分配。維護一個列表,記錄哪些地址是空閒的,在進行空間分配的時候從列表中找到符合大小的地址進行分配,而後更新列表。
  3. 將分配到的空間都初始化爲0值
  4. 設置對象頭:設置對象頭的信息,如GC年齡、"我是誰"。
  5. 調用init方法init方法是在編譯生成字節碼的時候生成的,做用爲初始化對象的成員變量(先初始化父類再初始化自己)。

  沒想到一個new操做居然經歷了這麼多,想一想確實有點任重而道遠的味道。可能有人說了,"我用spring不少bean都不用new也能正常使用的哦,你是否是在騙我哦?"對於這樣的提問,我只能說:spring

一邊去

  大哥,開個玩笑,你又何須當真呢,來,你先把手上的磚頭放下,我再給你扯一下子。其實對於spring,其在項目啓動的時候就已經進行了初始化,而且放在一個容器(IOC)中了,因此不是不須要只是工做提早作了(固然指的是單例模式);另外springBean的生命週期比咱們手動new出來的要更復雜一些,但本質上只不過是加了一些流程,讓其更具有擴展性,固然這都是題外話了(可是很重要)。數據庫

2. 類的加載流程

  一個對象建立的流程清楚了,可是某天面試官可能會說:"講下類的加載過程",這時候的你多是這樣的:安全

啥

  這個時候千萬別慌,先深吸一口氣,而後緩緩地說:"其實我面的是產品崗!"this

  在建立對象時第一步就是進行類的加載,可是類加載並非一步操做,而是有至關多的流程的(否則你覺得靜態變量是用愛賦值的嗎?),流程以下:類加載spa

  1. 加載:將類信息加載到JVM中,而且在內存中生成一個Class對象
  2. 驗證:驗證類的字節碼是否符合當前JVM
  3. 準備:將類的靜態變量初始化爲默認值。(static修飾的)
  4. 解析:將符號引用(一串字母)轉爲直接引用(內存地址引用)
  5. 初始化靜態變量初始化爲代碼中的值。例如static int a = 1a=1是在這一步進行的,第3步執行爲的時候a=0clinit方法在此步驟執行,跟init方法相似,先初始化父類再初始化自己。
  6. 使用
  7. 卸載

  上面就是類的整個加載流程,你可能一點沒記,沒有關係,來個例子體會體會。寫兩個類,一個父類,一個子類,設置日誌信息,而後調用查看結果。翻譯

import lombok.Data;

/**
 * 父類
 */
@Data
public class Father {

    private int age;

    private String name;

    public static String FATHER_STATIC = "FATHER_NAME";

    static {
        System.err.println("Father類的靜態塊:" + FATHER_STATIC);
    }

    public Father() {
        System.err.println("Father的構造方法");
    }

    public Father(int age, String name) {
        this.age = age;
        this.name = name;
    }

}


import lombok.Data;
/**
 * 子類(當前類)
 */
@Data
public class Son extends Father {

    private int sex;

    public static String SON_STATIC = "SON_NAME";

    static {
        System.err.println("Son類的靜態塊:" + SON_STATIC);
    }

    public Son() {
        System.err.println("Son的構造方法");
    }

    {
        // 驗證方法塊在構造方法前執行,不管位置在哪
        System.err.println("Son的構造方法塊");
    }

    public Son(int age, String name, int sex) {
        super(age, name);
        this.sex = sex;
    }

}
// 啓動,而後查看結果
public class main {

    public static void main(String[] args) {
        Son son1 = new Son();
        // 驗證類只加載一次
        Son son2 = new Son();
    }

}

  能夠想下這個小demo而後想下結果應該是什麼,而後對比下方的結果圖。3d

1594738023049

  理解了這個demo,對象和類的流程應該就沒啥問題了。其餘的如使用Class.forName調用、只用到父類變量會初始化當前類嗎之類的問題能夠本身動手驗證下,印象更加深入哦。指針

3. 類加載器

  類加載流程理解了,類加載器還會遠嗎?不遠了,就在下方了,否則就不起這個標題了。

  那麼類加載究竟是幹嗎的呢?廢話,確定是加載類的

別打我

  類加載器默認提供三種——BootStrap ClassLoaderExtClassLoaderAppClassLoader,你也能夠本身定義ClassLoader(只要繼承ClassLoader類,而後重寫loadClass方法就okay了)。

類加載器

  • BootStrap ClassLoader最頂層的類加載器,主要加載的是jre下lib目錄下的rt.jar包,因爲用的是C++編寫,因此在Java中表現的形式爲null;另外爲了安全考慮Java在加載jar包的時候用的文件名,而且只加載java、sun等開頭的類。
  • ExtClassLoader:第二層類加載器,範圍爲lib/ext目錄下的包
  • AppClassLoader:應用類加載器,範圍爲classpath下的jar包。

  正常類加載器加載類的過程是這樣的:

雙親委派模型

​ 這就是傳說中的雙親委派模型了,大概意思就是類要先從父類加載器加載,若是父類加載器加載了,那麼當前加載器就再也不加載,這樣能夠保證用戶在用的時候不會用到其餘人寫的重名或者惡意搞破壞的類;另外其實跟雙親沒什麼關係,只是名字這麼叫(那你說parent不翻譯成雙親應該怎麼翻譯嘛)。

破壞雙親委派模型

  雙親委派模型確實保證了Java庫類的安全性,可是還會帶來一些問題。

  思考一個問題:若是我在ExtClassLoader甚至BootStrap ClassLoader加載的類裏邊須要引用下層的類,那我要怎麼辦呢,按照雙親委派模型,我能拿到的類是從上面流下來的,可是我要的下面的類。

  因此,這個時候須要對這個模型進行一點改動,就是對於一些特定的類,其須要的一些類信息能夠從子類加載器中獲取注意這裏是特定的類,不是你想破壞就破壞的,只能是官方提供口子你才能破壞這個模型。

  拿經典的DriverManager來講,DriverManagerrt.jar下的類,由BootStrap ClassLoader加載,可是其須要管理各個數據庫廠商的DriverdriverManager加載圖

  從上圖能夠看出,若是嚴格遵守雙親委派模型是行不通的,這時候官方就在DriverManager中加了一個靜態塊來加載這些Driver類,這就是所謂的口子,來看下大概的代碼。

public class DriverManager {

	static {
    	// 加載下面的Driver
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }
    
     private static void loadInitialDrivers() {
        // ...
        // 這裏就是加載子類Driver的方法,內部實現經過一個上下文加載器完成,方法體在下方
        ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
        Iterator<Driver> driversIterator = loadedDrivers.iterator();
        // ...
        for (String aDriver : driversList) {
            try {
                println("DriverManager.Initialize: loading " + aDriver);
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: " + ex);
            }
        }
    }
    
    public static <S> ServiceLoader<S> load(Class<S> service) {
        // 簡單來講就是將加載好的類信息放入上下文加載器中,而後這邊從這個加載器拿
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }
}

  大概的邏輯就是:使用靜態塊先加載Driver的實現類,這些Driver實現類的信息被放入一個上下文加載器中,只要從這個上下文加載器拿出來就okay了。

  一開始可能都會被破壞雙親委派模型聽起來這麼牛的詞彙給嚇到,可是瞭解事後想來也不過如此,因此只要不停下來,道路就會不斷延伸。

慢一點,再慢一點。

相關文章
相關標籤/搜索