第三章 Java 字串處理加速器設計
3.2 類別初始化之實作
Java 初始化的類別方法有兩種。第一種為物件的初始化,此種類別方法即為
13
建構子的呼叫,負責 non-static field 的初始化。第二種為類別的初始化,負責 static field 的初始化。 物件初始化方法必須每個物件個別呼叫,因為每個物件的 non-static field 是私有的,所以必須分開初始化。而由於 static field 在相同類別或 相同父類別的物件中是共享的,類別初始化的方法在整個 Java 程式執行過程中只 需要呼叫一次。在 Java 規格書中,每個類別在被解析的時候,JVM 內部都會呼 叫此類別的類別初始化方法,而且這些方法也只能讓 JVM 內部呼叫,沒有任何語 法或 Java bytecode 可以讓程式開發者直接呼叫。
每一個宣告 static field 之類別都存在一個類別初始化方法。若一個類別不存 在任何自身宣告的 static field,則此類別不存在類別初始化方法。在 Java 程式語 言中,子類別若繼承父類別之 static field,那麼父類別與子類別會共享此 static field 的值。一個從父類別繼承來的 static field 並不需要初始化,因為在父類別呼叫過 類別初始化方法時,就已經將此 static field 初始化過,子類別不過是與父類別共 享此數值。
在我們的實作當中,類別初始化的時機是在每個類別的 static field 第一次被 讀取的時候進行,也就是說,在我們的 JAIP 中,並非與一般 JVM 類別初始化的 時間一樣在類別解析的時候,而是在每個類別 static field 第一次被讀取的時候初 始化。這種設計主要是考量有些類別的 static field 並沒有經常被使用,當某些類 別被解析過後,它們的 static field 從來沒被讀取過。因此,在我們的設計中,每 次進行 static field 讀取時,都要檢查此類別是否被初始化過,若尚未初始化則讓 JAIP 進入類別初始化方法的進入點。
Static filed 的讀取是透過 Java Bytecode─getstatic 來進行。此指令屬於類別操 作指令,這種指令在 decode stage 被解碼完畢後都會啟動動態解析器進行類別資 訊的解析。當 getstatic 指令被執行時,動態解析器都必須檢查使類別是否已被初 始化。每個 getstatic 指令後面都會接續一個運算元,此運算元用來表示 static field 在類別符號表中的條目編號,透過此編號,動態解析器可以取得一個 32 bit 的符
14
號資訊,如圖 2 前 16 bit 代表此 static field 所屬之類別 ID;後 16 bit 代表一個 Cross Reference Table 的 offset。動態解析器分別利用這 2 個 half-word 資訊進行 Cross Reference Table 與 Class Info Table 的存取,這時兩個 table 的讀取是並行的。
圖 2 動態解析器對 Cross Reference Table 與 Class Info Table 的查詢過程 一旦 isClassInitialized 旗號指示出類別尚未初始化,那麼 JAIP 忽略從 Cross Reference Table 中讀到的位址,並準備進行類別初始化方法的呼叫,過程如圖 2。
由於此時管線處於等待動態解析器的狀態,如果類別初始化方法的返回點想要記 錄在動態解析器中的某個狀態,那麼就會必須耗費更多的堆疊空間來儲存 return frame。為了不增加方法呼叫時 return frame 的大小,在本設計的類別初始化方法 返回點是設定在 getstatic 此指令本身。也就是說,類別初始化方法是在 getstatic 指令執行到一半時呼叫,而返回時,管線必須重新執行 getstatic 指令,動態解析 器也要重新解析。類別初始化方法底下都會有一到返回指令─return,return 指令 在管線中會映射到一組預先設置好的 j-code 序列,j-code 序列會透過 return frame 的資訊進行返回的操作。類別初始化方法呼叫過程如圖 3 返回方式是透過類別初 始化方法本身的 return 指令,返回位址是 getstatic 指令本身,返回後 getstatic 會 重新進入管線。
15
圖 3 類別初始化方法呼叫過程
解析過程如圖 4 所示。Get_L1_XRT_Ref 為 Class Symbol Table 的讀取,讀到 Cross Reference Table 的 offset 與類別 ID 後,進入 Offset_access 狀態同時進行 Cross Reference Table 與 Class Info Table 的讀取,若從 Cross Reference Table 讀到 0 的值,
代表此類別尚未解析,動態解析器將進入狀態 Illegal Offset 並以 interrupt 信號觸 發 RISC 核心的解析動作;若從 Cross Reference Table 讀到的值不為 0,此值代表 field 在堆中的位址。同一時間檢查 clinit_flag 值來判斷是否呼叫類別初始化方法,
若為 0,則代表此類別已經初始化過,直接進入 field_load 狀態進行 field 的讀取;
若 clinit_flag 為 1,進入 clinit_ret_frame 設置堆疊的 return frame。En_MA_mgt 及 Class_Loading 為觸發 class loader 對類別的讀取。Method_entry、Method_flag、
Max_stack 與 Max_local 分別進行 JPC 與堆疊的調整。
16
圖 4 動態解析器與類別初始化方法呼叫過程