1. 程式人生 > 其它 >樹莓派目標識別 python呼叫百度api通用物體識別 

樹莓派目標識別 python呼叫百度api通用物體識別 

類載入器

參考文章

物件是如何被建立的

類載入機制

Java類載入器——熱替換

1、類的生命週期

​ 載入 -> 驗證 -> 準備 -> 解析 -> 初始化 -> 使用 -> 解除安裝

​ 載入、驗證、準備、初始化、解除安裝這5個階段的順序是確定的,類的載入過程必須按照這種順序按部就班開始。但解析階段則不一定,在某些情況下可以在初始化階段後開始,為了支援Java語言的執行時繫結

1.1、載入:轉存至方法區並生成代理

​ JVM是使用雙親委派機制進行類載入,在描述前,有必要了解下該機制:

​ 若classLoader收到類載入請求,首先它自己不會嘗試載入該類,而是委託給父類去載入,每層載入器都是如此,最終會傳遞到頂層啟動類載入器。當父類載入器反饋自己無法完成(它的搜尋範圍內沒有找到所需載入類),子載入器會嘗試自己載入。

​ 該機制的優勢:確保一個類的全域性唯一性,當程式中出現多個限定名相同的類時,類載入器在載入的時候只會載入其中一個。

​ 類載入階段,JVM實際完成的任務:

1)通過類的全限定性類名獲取該類的二進位制位元組碼

2)將該位元組碼代表的靜態儲存結構轉換為方法區的執行時資料結構(將類的結構資訊放到方法去)

3)在記憶體(堆)中生成代表該類的Class物件(僅僅時.class的物件,而非例項),用來封裝類在方法區裡的資料結構,作為方法區中類的各種資料訪問介面

​ 從以上三步看出,通過Class獲取類的資料,像鏡子反射類的資訊,也是為什麼在用反射時要使用Class。類載入器根據類的全限定性名讀取此類的二進位制位元組流到JVM,儲存在執行時記憶體的方法區,並將其轉換為與目標型別對應的java.lang.Class物件例項

1.2、驗證:一切以 JVM 為重

​ 驗證作為連結的第一步,該階段的目的是確保Class檔案的子節流中包含的資訊符合當前虛擬機器的要求,不會對虛擬機器的自身安全產生威脅。之所以會準備這樣的一步,是因為虛擬機器能接受的Class並非都是Java原始碼編譯而來,也可以通過十六進位制編譯器直接編寫來產生。若是不對輸入的位元組碼檢查,對虛擬機器有威脅的子節流也會被載入,造成安全問題。

​ 格式校驗:是否符合Class檔案規範

​ 語義校驗:被final標記的型別是否有子類;被final修飾的方法是否被重寫;父類和子類無不相容的方法宣告

​ 操作校驗:運算元棧中資料必須正確操作,對常量池中符號引用驗證(解析階段會用到)

1.3、準備:小弟先行

​ 為類變數(靜態變數)分配記憶體並設定初始值(資料型別對應的‘零’值)

​ 即為靜態變數分配記憶體並對其初始值設為零,不包括靜態程式碼庫和例項變數。

​ (靜態程式碼塊在初始化的時候執行,例項變數在物件例項化的時候隨物件分配到Java堆中)

1.4、解析:引用替換


​ JVM 將常量池內的 符號引用 替換為 直接引用 的過程

​ 解析操作往往伴隨著 JVM 在執行完初始化後再執行

​ 解析動作主要針對類或者介面,欄位,類方法,介面方法,方法型別等。對應常量池中的CONSTANT_Class_info,CONSTANT_Fieldref_info,CONSTANT_Methodref_info。

​ 符號引用:任何形式的字面量,能描述所引目標並能在使用時無歧義的定位到即可,跟記憶體沒關係,有可能在硬碟或別的位置,任意資料儲存位置都行

​ 直接引用:能夠直接指向目標的指標、相對偏移量或任何間接定位目標的控制代碼。跟 JVM 的記憶體佈局有關,引用的目標必在記憶體

​ 驗證,準備,解析三階段合稱為連結階段,連結階段即將 JVM 中的二進位制子節流的類資訊 合併到 執行時狀態中。

1.5、初始化:跑 Java 程式碼了

​ 類載入的最後一步,在之前的過程中,除了載入階段使用者可以定製自定義類載入器參與,別的都是 JVM 的主導。在初始化這步,開始真正執行 Java 程式碼。小弟先行階段,靜態變數其實已經被賦值過,但僅為預設值,在初始化階段會將賦值程式中的指定值,再執行靜態程式碼塊。

​ 小小深入:初始化的階段也可以理解為 執行類的構造器方法的過程 (非類的構造方法)。構造器方法是() 方法,由編譯器自動收集類中所有靜態變數的賦值動作和靜態程式碼塊中的語句合併而成。需要注意的是,編譯器收集順序是語句在程式中的出現順序,因而靜態程式碼塊只能訪問到在其之前定義的值,而不能訪問到之後的值,但可以對之後的值進行賦值操作,因為在開始的時候變數已經被初始化了。

​ 構造器方法和建構函式不同,不需要顯示呼叫父類的構造器,虛擬機器會保證子類的構造器方法執行前,父類的已經執行過了,所以在 JVM 中第一個執行的構造器方法的類一定是大家的父類。由此引申到,子類的構造器可以引用父類構造好的值,但在實際測試中,子類構造器對父類的值會也不會發生影響(這裡是個疑問)

​ 類的載入過程到這裡就結束了,類載入的最終呈現效果是位於 堆中的Class物件,封裝了方法區內類的資料結構,並提供訪問方法區內資料結構的介面。但到目前為止,跟類的物件是沒關係的,目前只有類的靜態變數和靜態方法可用,物件需要我們自己去產生,更別說用普通成員變數和方法了。

2、類載入器角色

​ ClassFIle 在影盤上,可以理解為設計圖紙,這個圖紙要載入到 JVM 中根據圖紙創造多個例項

​ ClassFIle 載入到 JVM,被叫做DNA元資料模板,放在方法區

​ 檔案 -> JVM -> 元資料模板,需要運輸工具,類載入器在這中間扮演快遞員角色

3、建立物件

1)堆區分配物件需要的記憶體 (記憶體包括父類的所有例項變數,不包括靜態變數)

2)對例項預設初始值 (方法區內例項變數的定義拷貝到堆區,並賦預設值)

3)執行例項初始化程式碼 (初始化順序先父類後子類,執行順序先執行程式碼塊後執行構造方法)

​ (若有物件的引用,在棧區定義該物件型別的引用,再將堆區物件的地址賦值給他,每個子類物件持有父類物件的引用,可在內部通過super關鍵字呼叫父類物件,但外界不可訪問)

補充:通過例項呼叫例項方法時,先從方法區的物件型別資訊中查詢,找不到再去父類型別資訊中查詢。

若繼承層次比較深,而呼叫的方法位置在比較上層的父類中,呼叫效率是比較低的。比如用自定義型別去呼叫object類的方法,這樣查詢的層次就比較多了,要一層一層往上找。此時系統一般會採用 虛方法表的方法優化呼叫效率。

虛方法表:類載入時,為每個類建立表,包括該類的物件所有動態繫結的方法及地址,包含父類方法,一個方法一條記錄,子類重寫父類方法只會保留子類。當通過物件動態繫結方法時,查這個表就可以了,不用挨個查父類。

4、擴充套件

Java中,類的例項化分為兩個部分:類的載入和類的例項化,類的載入分為顯式和隱式。

平時常用的new建立例項,就是隱式的包含了類的載入過程。對於類的顯式載入,比較常用的是Class.forName()。實際上都是通過呼叫ClassLoader類的loadClass()方法,完成實際的載入工作。

類載入器classLoader載入類時有層次關係,Java中共4種類型:

BootStrapClassLoader 啟動類載入器 最高層次,載入jre/lib/rt.jar路徑下類或-XbootclassPath指定包

ExtensionClassLoader 擴充套件類載入器 預設載入%JAVA_HOME%lib/ext/*.jar

ApplicationClassLoader 應用類載入器 預設載入環境變數CLASSPATH中的設定值

CustomClassLoader 自定義類載入器 根據使用者需求定製載入過程,執行期指定類的動態實時載入

(熱載入也是基於該類的特性,繞過Java既定的載入規格)

類載入時,首先自底向上挨個檢查是否已經載入了指定類,若已載入,則直接返回該類的引用。若找到最高層也沒找到載入過該類,則開始自頂向下挨個嘗試載入,直到使用者自定義類載入器,還不成功,就會丟擲異常。