jvm高級特性

一. class類文件

  1. 常量池:字面量,符號引用 -> 訪問標誌:這個標誌用於識別一些類或者接口層次的訪問信息 -> 類索引、父類索引和接口索引集合都按順序排列在訪問標誌之後
  2.  描述符描述方法,字段,入參

例:int indexOf(char[]source,intsourceOffset,int sourceCount,char[]target,int targetOffset,int targetCount,intfromIndex)的描述符爲“([CII[CIII)I”

3. 屬性表 

  • Java程序方法體裏面的代碼經過Javac編譯器處理之後,最終變爲字節碼指令存儲在Code屬性內。在實例方法的局部變量表中至少會存在一個指向當前對象實例的局部變量,局部變量表中也會預留出第一個變量槽位來存放對象實例的引用,所以實例方法參數值從1開始計算

4. 字節碼

  • Java虛擬機的指令由一個字節長度的、代表着某種特定操作含義的數字(稱爲操作碼,Opcode)以及跟隨其後的零至多個代表此操作所需的參數(稱爲操作數,Operand)構成
  • 指令:[類型]load:將變量從局部變量表加載到操作數棧,[類型]store:將變量從操作數棧加載到局部變量表。(_<助記符操作數>)
  • 方法調用指令
  • 同步指令:(1). 方法級同步:方法級的同步是隱式的,無須通過字節碼指令來控制,它實現在方法調用和返回操作之中。虛擬機可以從方法常量池中的方法表結構中的ACC_SYNCHRONIZED訪問標誌得知一個方法是否被聲明爲同步方法。當方法調用時,調用指令將會檢查方法的ACC_SYNCHRONIZED訪問標誌是否被設置,如果設置了,執行線程就要求先成功持有管程,然後才能執行方法,最後當方法完成(無論是正常完成還是非正常完成)時釋放管程。在方法執行期間,執行線程持有了管程,其他任何線程都無法再獲取到同一個管程。如果一個同步方法執行期間拋出了異常,並且在方法內部無法處理此異常,那這個同步方法所持有的管程將在異常拋到同步方法邊界之外時自動釋放。(2). 同步塊monitorenter,monitorexit方法出異常無法處理執行monitorexit釋放鎖,異常表處理記錄異常。

二. 類加載

  1. 初始化的情況:(1). 遇到new、getstatic、putstatic或invokestatic這四條字節碼指令時,如果類型沒有進行過初始化,則需要先觸發其初始化階段(2). 反射調用(3). 虛擬機執行指定主類(main)(4). jdk7如果一個java.lang.invoke.MethodHandle實例最後的解析結果爲REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四種類型的方法句柄,並且這個方法句柄對應的類沒有進行過初始化,則需要先觸發其初始化(5). 接口中加入了default修飾的方法,實現類初始化之前先初始化接口。(常量不會引發初始化)(子類初始化之前父類需要初始化,接口用到才初始化)
  2. 加載

獲取類文件二進制字節流的過程不可控,用戶可自定義類加載器,二進制字節流被虛擬機存儲在方法區,實例化class對象作爲類型數據的訪問入口

     3. 驗證:二進制流存入方法區之前進行文件格式驗證,驗證完存入方法區,之後的驗證都在方法區中。元數據驗證進行類語義驗證。字節碼驗證類的方法體驗證。符號引用驗證。

     4. 準備:類中定義的變量分配內存(static零值,常量賦值)

     5. 解析;符號引用->直接引用

        類D要把類C的符號引用轉化爲直接引用,類D的類加載器通過C的全限定類名加載,進行加載到方法區需要元數據,字節碼驗證引出了其他類的加載。

    6. 初始化:初始化階段是執行類構造器<clinit>的過程,編譯器自動收集所有類變量的賦值動作和static塊中語句合併產生的。靜態語句塊中只能訪問到定義在靜態語句塊之前的變量,定義在它之後的變量,在前面的靜態語句塊可以賦值,但是不能訪問。父類的<clinit>要先執行,所以父類的靜態語句塊要優於子類的變量賦值。(接口則不同使用時纔會執行clinit初始化)而且clinit只執行一次,併發阻塞的其他線程即使被喚醒也不會執行<clinit>.

三.類加載器及雙親委派

  1. 啓動類加載器c++實現是虛擬機的一部分,加載加載存放在<JAVA_HOME>\lib目錄,或者被-Xbootclasspath參數所指定的路徑中存放的,而且是Java虛擬機能夠識別的(按照文件名識別,如rt.jar、tools.jar,名字不符合的類庫即使放在lib目錄中也不會被加載)類庫加載到虛擬機的內存中。

四. 字節碼執行引擎

  1. 棧楨:操作數棧,局部變量表,返回地址,動態鏈接(class字節碼方法表的code屬性中寫入)
  2. 局部變量表:64位可能佔兩個變量槽,作爲GCRoot變量槽被佔用不會被回收
  3. 動態鏈接:字節碼指令調用方法的入參就是常量池中方法的符號引用。
  4. 解析:class文件中存的是方法的符號引用,因此一部分在類加載解析過程中轉化直接引用,一部分運行時動態轉化,類加載過程中能夠確定的是不可變的能確定版本的方法(主要有靜態方法和私有方法兩大類invokestatic,invokespecial,final)
  5. 分派:(1). 靜態分派是重載的本質,編譯期間靜態類型可確定 (2).動態分派是重寫的本質運行期間執行invokevirtual指令。

1)找到操作數棧頂的第一個元素所指向的對象的實際類型,記作C。

2)如果在類型C中找到與常量中的描述符和簡單名稱都相符的方法,則進行訪問權限校驗,如果通過則返回這個方法的直接引用,查找過程結束;不通過則返回java.lang.IllegalAccessError異常。

3)否則,按照繼承關係從下往上依次對C的各個父類進行第二步的搜索和驗證過程。

4)如果始終沒有找到合適的方法,則拋出java.lang.AbstractMethodError異常。

Human human = new Man();這個過程首先執行new指令創建實例,實例存入局部變量表,然後壓倒棧頂,執行invokeVirtual指令。棧頂第一個元素因爲是子類對象所以重寫會調用子類方法。new指令之後是dup指令把對象引用存入局部變量表,之後纔是invokevirtual。

優化動態分派,類中會有虛方法表保存虛方法的實際入口地址,子類中包含父類所有的方法。連接階段初始化虛方法表。

五. 解釋器與編譯器

解釋執行逐行翻譯字節碼爲機器碼,編譯的優勢在於以方法爲單位進行優化其中包含方法內聯,逃逸分析等等優化操作。

  1. 當一個方法被調用虛擬機會先檢查該方法有無本地編譯完成的版本有則執行,無則統計方法調用計數器與循環回邊之和是否超過方法調用計數器的閾值。超過閾值向即時編譯器提交編譯請求。

2. 方法內聯

只有使用invokespecial指令調用的私有方法、實例構造器、父類方法和使用invokestatic指令調用的靜態方法纔會在編譯期進行解析。編譯器可確定進行方法內聯即把被調用的方法代碼直接放置調用方法內。虛方法內聯則提供類型繼承分析,用於確定在目前已加載的類中,某個接口是否有多於一種的實現、某個類是否存在子類、某個子類是否覆蓋了父類的某個虛方法等信息。這樣,編譯器在進行內聯時就會分不同情況採取不同的處理:如果是非虛方法,那麼直接進行內聯就可以了,這種的內聯是有百分百安全保障的;如果遇到虛方法,則會向CHA查詢此方法在當前程序狀態下是否真的有多個目標版本可供選擇,如果查詢到只有一個版本,那就可以假設“應用程序的全貌就是現在運行的這個樣子”來進行內聯,這種內聯被稱爲守護內聯(Guarded Inlining)

3. 逃逸分析

分析對象動態作用域,當一個對象在方法裏面被定義後,它可能被外部方法所引用,例如作爲調用參數傳遞到其他方法中,這種稱爲方法逃逸;甚至還有可能被外部線程訪問到,譬如賦值給可以在其他線程中訪問的實例變量,這種稱爲線程逃逸;從不逃逸、方法逃逸到線程逃逸,稱爲對象由低到高的不同逃逸程度。

  • 棧上分配:對於不會被外部方法或線程訪問到的對象則會在棧上分配隨方法結束銷燬。
  • 標量替換:對象創建時確定其不被外部訪問則拆散對象將其成員變量在棧幀中創建。
  • 同步消除:確定不會被外部線程訪問,同步也可消除。

4.公共子表達式消除

5.數組邊界檢查消除

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