1. 程式人生 > 實用技巧 >CodeForces 954D-Fight Against Traffic(加邊最短路)

CodeForces 954D-Fight Against Traffic(加邊最短路)

1.1概述

我們知道,一個.java檔案在編譯後會形成相應的一個或多個Class檔案,這些Class檔案中描述了類的各種資訊,並且它們最終都需要被載入到虛擬機器中才能被執行和使用。事實上,虛擬機器把描述類的資料從Class檔案載入到記憶體,並對資料進行校驗,轉換解析和初始化,最終形成可以被虛擬機器直接使用的Java型別的過程就是虛擬機器的類載入機制。與那些在編譯時需要進行連線工作的語言不同,在Java語言裡面,型別的載入和連線都是在程式執行期間完成,這樣會在類載入時稍微增加一些效能開銷,但是卻能為Java應用程式提供高度的靈活性,Java中天生可以動態擴充套件的語言特性多型就是依賴執行期動態載入

動態連結這個特點實現的。例如,如果編寫一個使用介面的應用程式,可以等到執行時再指定其實際的實現。這種組裝應用程式的方式廣泛應用於Java程式之中。

1.2詳解

Java類從被載入到虛擬機器記憶體中開始,到卸載出記憶體為止,它的整個生命週期包括:載入(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using) 和 解除安裝(Unloading)七個階段。其中準備、驗證、解析3個部分統稱為連線(Linking),如圖所示:

其中載入、驗證、準備、初始化、解除安裝是限定了順序的,而其他步驟則不一定是按照上述順序。
下面是會發生類的初始化的情況。

  • 遇到new、getstatic、putstatic或invokestatic這四條位元組碼指令(注意,newarray指令觸發的只是陣列型別本身的初始化,而不會導致其相關型別的初始化,比如,new String[]只會直接觸發String[]類的初始化,也就是觸發對類[Ljava.lang.String的初始化,而直接不會觸發String類的初始化)時,如果類沒有進行過初始化,則需要先對其進行初始化。生成這四條指令的最常見的Java程式碼場景是:
    使用new關鍵字例項化物件的時候
    讀取或設定一個類的靜態欄位(被final修飾,已在編譯器把結果放入常量池的靜態欄位除外)的時候
    呼叫一個類的靜態方法的時候
  • 使用java.lang.reflect包的方法對類進行反射呼叫的時候,如果類沒有進行過初始化,則需要先觸發其初始化。
  • 當初始化一個類的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類的初始化。
  • 當虛擬機器啟動時,使用者需要指定一個要執行的主類(包含main()方法的那個類),虛擬機器會先初始化這個主類。
  • 使用jdk1.7動態語言支援時,如果一個java.lang.invoke.MethodHandle例項最後的解析結果REF_getstatic,REF_putstatic,REF_invokeStatic的方法控制代碼,並且這個方法控制代碼所對應的類沒有進行初始化,則需要先出觸發其初始化。

1、載入(Loading)
通過一個類的全限定名來獲取定義此類的二進位制位元組流,將這個位元組流所代表的靜態儲存結構轉化為方法區的執行時資料結構,在記憶體中(對於HotSpot虛擬就而言就是方法區)生成一個代表這個類的java.lang.Class物件,作為方法區這個類的各種資料的訪問入口
2、驗證(Verification)
驗證是連線階段的第一步,這一階段的目的是為了確保Class檔案的位元組流中包含的資訊符合當前虛擬機器的要求,並且不會危害虛擬機器自身的安全。
3、準備(Preparation)
準備階段是正式為類變數(static 成員變數)分配記憶體並設定類變數初始值(零值)的階段,這些變數所使用的記憶體都將在方法區中進行分配。這裡的初始值並不是我們設定的值,而且系統預設的零值,比如:
public static int value = 123;
value 的值會被設定成0而不是123,如果加上final 修飾:
public static final int value = 123;
則會設定成123。
4、解析(Resolution)
解析階段是虛擬機器將常量池內的符號引用替換為直接引用的過程。解析動作主要針對類或介面、欄位、類方法、介面方法、方法型別、方法控制代碼和呼叫點限定符7類符號引用進行。
5、初始化(Initialization)
類初始化階段是類載入過程的最後一步。在前面的類載入過程中,除了在載入階段使用者應用程式可以通過自定義類載入器參與之外,其餘動作完全由虛擬機器主導和控制。到了初始化階段,才真正開始執行類中定義的java程式程式碼(位元組碼)。
初始化階段是執行類構造器()方法的過程。()方法是由編譯器自動收集類中的所有類變數的賦值動作和靜態語句塊static{}中的語句合併產生的,編譯器收集的順序是由語句在原始檔中出現的順序所決定的,靜態語句塊只能訪問到定義在靜態語句塊之前的變數,定義在它之後的變數,在前面的靜態語句塊可以賦值,但是不能訪問。
例如:

static{
        i=0;
        System.out.println(i);//Error:Cannot reference a field before it is defined(非法向前應用)
    }
    static int i=1;

會報錯

 static{
        i=0;
        //System.out.println(i);
    }

    static int i=1;

卻能通過。
類構造器()與例項構造器()不同,它不需要程式設計師進行顯式呼叫,虛擬機器會保證在子類類構造器()執行之前,父類的類構造()執行完畢。而且虛擬機器會保證一個類的類構造器()在多執行緒環境中被正確的加鎖、同步,如果多個執行緒同時去初始化一個類,那麼只會有一個執行緒去執行這個類的類構造器(),其他執行緒都需要阻塞等待,直到活動執行緒執行()方法完畢。特別需要注意的是,在這種情形下,其他執行緒雖然會被阻塞,但如果執行()方法的那條執行緒退出後,其他執行緒在喚醒之後不會再次進入/執行()方法,因為 在同一個類載入器下,一個型別只會被初始化一次。