首頁 > 軟體

JVM之方法返回地址詳解

2022-02-28 16:06:14

JVM之方法返回地址

JVM執行時資料區的虛擬機器器棧的棧幀中包含了返回地址

當一個方法開始執行後,只有兩種方式可以退出這個方法。

  • 第一種方式是執行引擎遇到任意一個方法返回的位元組碼指令(例如:areturn),這時候可能會有返回值傳遞給上層的方法呼叫者(呼叫當前方法的方法稱為呼叫者),是否有返回值和返回值的型別將根據遇到何種方法返回指令來決定,這種退出方法的方式稱為正常完成出口(Normal Method Invocation Completion)。
  • 另外一種退出方式是,在方法執行過程中遇到了異常,並且這個異常沒有在方法體內得到處理,無論是Java虛擬機器器內部產生的異常,還是程式碼中使用 athrow 位元組碼指令產生的異常,只要在本方法的例外處理器表中沒有搜尋到匹配的例外處理器,就會導致方法退出,這種退出方法的方式稱為異常完成出口(Abrupt Method Invocation Completion)。一個方法使用異常完成出口的方式退出,是不會給它的上層呼叫者產生任何返回值的。
  • 無論採用何種退出方式,在方法退出之後,都需要返回到方法被呼叫的位置,程式才能繼續執行,方法返回時可能需要在棧幀中儲存一些資訊,用來幫助恢復它的上層方法的執行狀態。

一般來說,方法正常退出時,呼叫者的程式計數器的值可以作為返回地址,棧幀中很可能會儲存這個計數器值。而方法異常退出時,返回地址是要通過例外處理器表來確定的,棧幀中一般不會儲存這部分資訊。

方法退出的過程實際上就等同於把當前棧幀出棧,因此退出時可能執行的操作有:恢復上層方法的區域性變數表和運算元棧,把返回值(如果有的話)壓入呼叫者棧幀的運算元棧中,調整程式計數器的值以指向方法呼叫指令後面的一條指令等。

小結一下

虛擬機器器會使用針對每種返回型別的操作來返回,返回值將從運算元棧出棧並且入棧到呼叫方法的方法棧幀中,當前棧幀出棧,被呼叫方法的棧幀變成當前棧幀,程式計數器將重置為呼叫這個方法的指令的下一條指令。

方法返回地址以及棧幀中的附加資訊

方法返回地址

1 點睛

存放呼叫該方法的 pc 暫存器的值。

一個方法的結束,有兩種方式。

  • 正常執行完成。
  • 出現未處理的異常,非正常退出。

無論通過哪種方式退出,在方法退出後都返回到該方法被呼叫的位置。方法正常退出時,呼叫者的pc計數器的值作為返回地址,即呼叫該方法的指令的下一條指令的地址。而通過異常退出的,返回地址是要通過異常表來確定,棧幀中一般不會儲存這部分資訊。

當一個方法開始執行後,只有兩種方式可以退出這個方法。

a 執行引擎遇到任意一個方法返回的位元組碼指令(return),會有返回值傳遞給上層的方法呼叫者,簡稱正常完成出口。

  • 一個方法在正常呼叫完成之後,究竟需要使用哪一個返回指令,還需要根據方法返回值的實際資料型別而定。
  • 在位元組碼指令中,返回指令包含ireturn(當返回值是boolean,byte,char,short和int型別時使用),lreturn(Long型別),freturn(Float型別),dreturn(Double型別),areturn。另外還有一個return指令宣告為void的方法,範例初始化方法,類和介面的初始化方法使用。

b 在方法執行過程中遇到異常(Exception),並且這個異常沒有在方法內進行處理,也就是隻要在本方法的異常表中沒有搜尋到匹配的例外處理器,就會導致方法退出,簡稱異常完成出口。

方法執行過程中,丟擲異常時的例外處理,儲存在一個例外處理表,方便在發生異常的時候找到處理異常的程式碼

2 看程式碼吧

import java.io.FileReader;
import java.io.IOException;
import java.util.Date; 
/**
* 返回指令包含ireturn(當返回值是 boolean、byte、char、short和int型別時使用)、
* lreturn、freturn、dreturn以及areturn,
* 另外還有一個return指令供宣告為void的方法、
* 範例初始化方法、類和介面的初始化方法使用。
*/
public class ReturnAddressTest {
    public boolean methodBoolean() {
        return false;
    }
 
    public byte methodByte() {
        return 0;
    }
 
    public short methodShort() {
        return 0;
    }
 
    public char methodChar() {
        return 'a';
    }
 
    public int methodInt() {
        return 0;
    }
 
    public long methodLong() {
        return 0L;
    }
 
    public float methodFloat() {
        return 0.0f;
    }
 
    public double methodDouble() {
        return 0.0;
    }
 
    public String methodString() {
        return null;
    }
 
    public Date methodDate() {
        return null;
    }
 
    public void methodVoid() {
    } 
 
    static {
        int i = 10;
    }
 
    public void method2() {
        methodVoid();
        try {
            method1();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
    public void method1() throws IOException {
        FileReader fis = new FileReader("atguigu.txt");
        char[] cBuffer = new char[1024];
        int len;
        while ((len = fis.read(cBuffer)) != -1) {
            String str = new String(cBuffer, 0, len);
            System.out.println(str);
        }
        fis.close();
    }
}

3 解析後的結果

public boolean methodBoolean();
    descriptor: ()Z
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: iconst_0
         1: ireturn
      LineNumberTable:
        line 15: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       2     0  this   Lcom/atguigu/java3/ReturnAddressTest;
 
  public byte methodByte();
    descriptor: ()B
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: iconst_0
         1: ireturn
      LineNumberTable:
        line 19: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       2     0  this   Lcom/atguigu/java3/ReturnAddressTest; 
 
  public short methodShort();
    descriptor: ()S
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: iconst_0
         1: ireturn
      LineNumberTable:
        line 23: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       2     0  this   Lcom/atguigu/java3/ReturnAddressTest; 
 
  public char methodChar();
    descriptor: ()C
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: bipush        97
         2: ireturn
      LineNumberTable:
        line 27: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       3     0  this   Lcom/atguigu/java3/ReturnAddressTest; 
 
  public int methodInt();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: iconst_0
         1: ireturn
      LineNumberTable:
        line 31: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       2     0  this   Lcom/atguigu/java3/ReturnAddressTest; 
 
  public long methodLong();
    descriptor: ()J
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: lconst_0
         1: lreturn
      LineNumberTable:
        line 35: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       2     0  this   Lcom/atguigu/java3/ReturnAddressTest; 
 
  public float methodFloat();
    descriptor: ()F
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: fconst_0
         1: freturn
      LineNumberTable:
        line 39: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       2     0  this   Lcom/atguigu/java3/ReturnAddressTest; 
 
  public double methodDouble();
    descriptor: ()D
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: dconst_0
         1: dreturn
      LineNumberTable:
        line 43: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       2     0  this   Lcom/atguigu/java3/ReturnAddressTest; 
 
  public java.lang.String methodString();
    descriptor: ()Ljava/lang/String;
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aconst_null
         1: areturn
      LineNumberTable:
        line 47: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       2     0  this   Lcom/atguigu/java3/ReturnAddressTest; 
 
  public java.util.Date methodDate();
    descriptor: ()Ljava/util/Date;
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aconst_null
         1: areturn
      LineNumberTable:
        line 51: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       2     0  this   Lcom/atguigu/java3/ReturnAddressTest; 
 
  public void methodVoid();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 56: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  this   Lcom/atguigu/java3/ReturnAddressTest; 
 
  public void method2();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=2, args_size=1
         0: aload_0
         1: invokevirtual #2                  // Method methodVoid:()V
         4: aload_0
         5: invokevirtual #3                  // Method method1:()V
         8: goto          16
        11: astore_1
        12: aload_1
        13: invokevirtual #5                  // Method java/io/IOException.printStackTrace:()V
        16: return
      Exception table:
         from    to  target type
             4     8    11   Class java/io/IOException
      LineNumberTable:
        line 63: 0
        line 65: 4
        line 68: 8
        line 66: 11
        line 67: 12
        line 69: 16
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           12       4     1     e   Ljava/io/IOException;
            0      17     0  this   Lcom/atguigu/java3/ReturnAddressTest;
      StackMapTable: number_of_entries = 2
        frame_type = 75 /* same_locals_1_stack_item */
          stack = [ class java/io/IOException ]
        frame_type = 4 /* same */ 
 
  public void method1() throws java.io.IOException;
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=5, locals=5, args_size=1
         0: new           #6                  // class java/io/FileReader
         3: dup
         4: ldc           #7                  // String atguigu.txt
         6: invokespecial #8                  // Method java/io/FileReader."<init>":(Ljava/lang/String;)V
         9: astore_1
        10: sipush        1024
        13: newarray       char
        15: astore_2
        16: aload_1
        17: aload_2
        18: invokevirtual #9                  // Method java/io/FileReader.read:([C)I
        21: dup
        22: istore_3
        23: iconst_m1
        24: if_icmpeq     50
        27: new           #10                 // class java/lang/String
        30: dup
        31: aload_2
        32: iconst_0
        33: iload_3
        34: invokespecial #11                 // Method java/lang/String."<init>":([CII)V
        37: astore        4
        39: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;
        42: aload         4
        44: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        47: goto          16
        50: aload_1
        51: invokevirtual #14                 // Method java/io/FileReader.close:()V
        54: return
      LineNumberTable:
        line 72: 0
        line 73: 10
        line 75: 16
        line 76: 27
        line 77: 39
        line 78: 47
        line 79: 50
        line 80: 54
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           39       8     4   str   Ljava/lang/String;
            0      55     0  this   Lcom/atguigu/java3/ReturnAddressTest;
           10      45     1   fis   Ljava/io/FileReader;
           16      39     2 cBuffer   [C
           23      32     3   len   I
      StackMapTable: number_of_entries = 2
        frame_type = 253 /* append */
          offset_delta = 16
          locals = [ class java/io/FileReader, class "[C" ]
        frame_type = 252 /* append */
          offset_delta = 33
          locals = [ int ]
    Exceptions:
      throws java.io.IOException
 
  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=1, args_size=0
         0: bipush        10
         2: istore_0
         3: return
      LineNumberTable:
        line 59: 0
        line 60: 3
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
}

說明

可以觀察一下各種方法的 return 位元組碼指令到底是什麼。

體會一下異常表。

      Exception table:

         from    to  target type

            4     8      11    Class java/io/IOException

本質上,方法的退出就是當前棧幀出棧的過程。此時,需要恢復上層方法的區域性變數表、運算元棧、將返回值壓入呼叫者棧幀的運算元棧、設定PC暫存器值等,讓呼叫者方法繼續執行下去。

正常完成出口和異常完成出口的區別在於:通過異常完成出口退出的不會給他的上層呼叫者產生任何的返回值。

一些附加資訊

棧幀中還允許攜帶與 Java 虛擬機器器實現相關的一些附加資訊。例如:對程式偵錯提供支援的資訊。

以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com