JVM系列-方法呼叫的原理

吳七羊發表於2020-07-14

JVM系列-方法呼叫的原理

最近重新看了一些JVM方面的筆記和資料,收穫頗豐,尤其解決了長久以來心中關於JVM方法管理的一些疑問。下面介紹一下JVM中有關方法呼叫的知識。

目的

方法呼叫,目的是選擇方法正確的執行版本,也就是找到方法的入口地址。

方法呼叫指令

方法呼叫的位元組碼指令一共有五種,分別是:

  1. invokestatic:
    • 類方法:static
  2. invokespecial:
    • 方法:例項構造器
    • 私有方法:private
    • 父類中的方法
  3. invokevirtual
    • 虛方法
    • final修飾的方法
  4. invokeinterface
    • 介面方法
  5. invokedynamic
    • 用於動態語言支援

方法分類

非虛方法:

  1. 特點:
    • 在類載入時期,有些方法的呼叫版本已經能夠確定,在類載入-解析階段,把這些方法的符號引用替換為直接引用(即入口地址)。
  2. 包括:
    1. 類方法
    2. 方法
    3. 私有方法
    4. 父類中的方法
    5. final方法

即由invokestatic、invokespecial指令呼叫的方法,以及final修飾的方法,屬於非虛方法

虛方法:

  1. 特點:
    • 在類載入時期,無法確定方法最終的呼叫版本
  2. 包括:
    • 非非虛方法

即由invokevirtual呼叫的方法,除了final修飾的之外,都是虛方法

呼叫方式

解析

  1. 定義:對於非虛方法,在類載入的時候已經能確定這些方法的呼叫版本,在類載入的解析階段把符號引用替換為直接引用,就是方法的入口地址
  2. 範圍:非虛方法

分派

靜態分派

  1. 定義:在編譯期,根據方法涉及的引用型別(包括引數列表的引用型別和方法的呼叫者的引用型別)來確定方法呼叫的(初步)版本,並把相應的符號引用放在位元組碼指令中,這個步驟叫做靜態分派
  2. 範圍:虛方法、非虛方法
  3. 應用:方法的過載

動態分派

  1. 定義:在執行時,根據物件的實際型別來確定方法的呼叫版本,這個步驟叫做動態分派
  2. 範圍:虛方法
  3. 應用:方法的重寫
  4. 原理:
    1. 方法呼叫的部分位元組碼指令:
      1. aload:在呼叫方法時,總是有該指令將實際型別的物件引用入棧
      2. 載入方法引數
      3. invoketual
        1. 先在棧頂取到實際型別的物件引用
        2. 根據方法的符號引用在本類中尋找,找不到的話依次向上在父類中找該方法
    2. 優化實現:在虛擬機器中,虛方法表是動態分派的一種優化實現
      1. 虛方法表:
        1. 定義:類資訊的一種,在類載入-準備階段完成初始化,存放在方法區
        2. 特點:
          1. 虛方法表中儲存方法的入口地址
          2. 子類中繼承但未重寫的方法,在子類的虛方法表中存放的入口地址,就是父類的虛方法表中的入口地址,指向父類的實現
          3. 在父子類中,相同符號引用的方法(重寫)的方法,其在各自虛方法表中的索引相同
      2. 解釋動態分派:
        1. 根據位元組碼指令的符號引用,找到該方法在父類虛方法表中的索引
        2. 切換到實際型別的虛方法表,通過該索引,找到方法的入口地址

總結

主要內容在於方法呼叫的方式,即如何找到方法的入口地址的過程。

在這三種方式中,靜態分派發生在編譯期,解析發生在類載入時,動態分派發生在執行時。針對三者作用物件的不同可知,靜態分派是可以和解析或動態分派同時發生的,如類方法依然可以過載,重寫的方法也可以過載。但解析和動態分派是不可共存的。

以上是我的分享,如有疑問或錯誤,請指出,一起學習。

參考

《深入理解Java虛擬機器_JVM高階特性與最佳實踐-第3版》

相關文章