当前位置:网站首页>卷起來,突破35歲焦慮,動畫演示CPU記錄函數調用過程

卷起來,突破35歲焦慮,動畫演示CPU記錄函數調用過程

2022-07-05 09:45:00 hi-dhl

hi 大家好,我是 DHL。公眾號:ByteCode ,專注分享有趣硬核原創內容,Kotlin、Jetpack、性能優化、系統源碼、算法及數據結構、動畫、大廠面經

全文分為 視頻版文字版

視頻版:

通過語音和動畫,能够更加直觀的看到,內存記錄方法調用和返回過程。

bilibili 地址: b23.tv/TQXL4xx

文字版

我們在寫代碼的時候有沒有思考過 方法如何調用方法執行完之後如何返回內存如何記錄方法調用過程 。而這也是今天這篇文章重點內容。

方法調用和返回過程涉及到了,虛擬機棧、程序計數器、局部變量錶、操作數棧、方法返回地址、動態鏈接等等內容,涉及到知識點很多,同時這些內容也是高頻面試題,所以我將拆分成多篇文章,針對每個知識點去做詳細的分析。而今天這篇文章我們重點去看內存是如何記錄方法調用和返回過程。

虛擬機棧

Java 方法以棧幀的形式,運行在虛擬機棧(Java 棧)中,棧是線程私有的,程序啟動的時候,會創建一個 main 線程,操作系統會為每一個線程分配一段內存,線程創建的時候會創建一個虛擬機棧,虛擬機棧的生命周期和線程一樣,線程結束了,虛擬機棧也銷毀了。

每個 Java 方法,對應一個個棧幀,所以方法開始和結束,都是一個個棧幀入棧和出棧的過程,效果如下圖所示。

棧幀

每個 Java 方法,都是一個個棧幀,每個棧幀包括了:局部變量錶、操作數棧、方法返回地址、動態鏈接、附加信息。

  • 局部變量錶: 保存方法參數列錶和方法內的局部變量,按照聲明的順序存儲,它以數組的形式展示,如果是實例方法,索引為 0 是 this 的引用,如下圖所示。

索引(Slot)名字(Name)
0this
1num
2res
  • 操作數棧: 保存方法執行過程中的臨時結果
  • 返回地址: 保存調用該方法的 pc 寄存器的值(即 JVM 指令地址),用於方法結束時,返回調用處,讓調用者方法繼續執行下去
  • 動態鏈接: 指向運行時常量池中該棧幀所屬方法的引用,即從常量池中找到目標方法的符號引用,然後轉換為直接引用
  • 附加信息:比如程序 debug 時添加的一些附件信息(不重要,不需要關心,可忽略)

這裏只需要知道它們的作用即可,它們的數據結構、字節碼的含義、執行過程等等,後續的文章我將會針對每個知識點去做詳細的分析。

方法調用過程

先寫一段方法調用的代碼,首先會調用 main() 方法之後調用 fun1() 然後調用 fun2() ,如下圖所示。

現在我們來演示一下 Java 虛擬機執行這些 JVM 指令的過程,首先會調用 main () 方法。

main () 方法

main() 方法執行流程動畫效果如下所示。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ApkZcRTw-1654832156342)(https://img.hi-dhl.com/16541790180176.gif)]

  1. 執行指令 0: aload_0,從局部變量錶中,讀取索引為 0 的值,壓入操作數棧中,因為是實例方法,所以索引為 0 的值是 this 的引用

  1. 執行指令 1: iconst_5 ,將常量 5 壓入操作數棧中

  1. 執行指令 2: invokevirtual #7,常量 5 和 this 從操作數棧中出棧,然後調用 this.fun1(5)

首先從常量池中找到方法 fun1() 的符號引用,然後通過動態鏈接將符號引用轉換成直接引用,之後調用 this.fun1(5),將方法 fun1() 作為棧幀壓入虛擬機棧中,跳轉到 fun1(),繼續往下執行。

如何從常量池中找到目標方法的符號引用,然後轉換成直接引用的過程,將會在後面系列文章中詳細分析

在調用 fun1() 之前,fun1() 的局部變量錶和方法返回地址已經確定好了。

進入方法 fun1 (int num)

方法 fun1(int num) 執行流程動畫效果如下所示。

  1. 執行指令 0: aload_0,從局部變量錶中,讀取索引為 0 的值,壓入操作數棧中。因為是實例方法,所以索引為 0 的值是 this 的引用

  1. 執行指令 1: iload_1,從局部變量錶中,讀取索引為 1 變量 num 的值,並壓入操作數棧

  1. 執行指令 2: invokevirtual #13,num 和 this 從操作數棧中出棧,然後調用 this.fun2(num)

首先從常量池中找到方法 fun2() 的符號引用,然後通過動態鏈接將符號引用轉換成直接引用,之後調用 this.fun2(num),將方法 fun2() 作為棧幀壓入虛擬機棧中,跳轉到 fun2(),繼續往下執行,調用 fun2() 之前,fun2() 的局部變量錶和方法返回地址已經確定好了。

進入方法 fun2 (int num)

方法 fun2(int num) 執行流程動畫效果如下所示。

  1. 執行指令 0: iload_1,從局部變量錶中,讀取索引為 1 變量 num 的值,並壓入操作數棧中

  1. 執行指令 1: bipush ,將常量 10 壓入操作數棧中

  1. 執行指令 3: iadd ,num 和常量 10 從操作數棧中出棧,然後進行相加,將結果壓入操作數棧中

  1. 執行指令 4: istore_2,從操作數棧中取出結果,並賦值給局部變量錶中索引為 2 的變量 res2

  1. 執行指令 5: iload_2,從局部變量錶中,讀取索引為 2 的變量 res2 的值,並壓入操作數棧中

方法退出過程

每個方法即是一個棧幀,每個棧幀會保存方法返回地址,執行 return 系列指令時,會根據當前棧幀保存的返回地址,返回到調用的比特置,繼續往下執行。因此會分為兩種情况。

异常退出

如果出現了异常且捕獲了該异常,則會從异常錶裏查找 PC 寄存器的地址(JVM 指令地址),返回調用處繼續執行。

正常退出時會做以下件事

  • 恢複上一個棧幀局部變量錶、操作數棧
  • 如果有返回值,將返回值壓入調用者棧幀的操作數棧,是否有返回值根據 return JVM 指令:
    • ireturn :返回值是 booleanbytecharshortint 類型
    • lreturn :返回值是 Long 類型
    • freturn :返回值是 Float 類型
    • dreturn :返回值是 Double 類型
    • areturn :返回值是引用類型
    • return :返回值類型為 void
  • 設置調用者棧幀的 JVM 指令地址
  • 當前棧幀從虛擬機棧中出棧

這篇文章我們只分析正常退出流程,异常退出和正常退出的流程大致都是一樣的。正常退出流程動畫效果如下所示。

  1. 方法 fun2 結束時,執行最後一條指令 ireturn ,當前棧幀從虛擬機棧中出棧,根據當前棧幀保存的方法返回地址,返回到 fun1 ,恢複 fun1 的局部變量錶、操作數棧,將返回結果 res2 保存到調用者(fun1)操作數棧中

  1. 回到方法 fun1(int num) ,執行指令 5: istore_2,變量 res2 從操作數中出棧,賦值給局部變量錶中索引為 2 的變量 res1

  1. 執行指令 6: iload_2 ,從局部變量錶中,讀取索引為 2 變量 res1 的值,並壓入操作數棧中

  1. 執行方法 fun1 最後一條指令 7: ireturn,當前棧幀從虛擬機棧中出棧,根據當前棧幀保存的方法返回地址,返回到 main 方法,恢複 main 方法的局部變量錶、操作數棧,將返回結果 res2 保存到調用者(main)操作數棧中

  1. 最後回到方法 main 中,執行最後一條指令 5: pop,操作數棧中的元素出棧

  1. 執行最後一條指令 6: return,至此所有的方法調用結束了,方法所占用的內存,也將返回給系統

每次的方法調用,即是棧幀入棧和出棧的過程,同時也需要占用部分內存,用於保存局部變量錶、操作數棧、方法返回地址、動態鏈接、附加信息等等。

當方法執行完,即棧幀出棧,方法調用所占用的內存,也將返回給系統。


全文到這裏就結束了,感謝你的閱讀,如果有幫助,歡迎 在看點贊收藏分享 給身邊的朋友。

真誠推薦你關注我,公眾號:ByteCode ,持續分享硬核原創內容,Kotlin、Jetpack、性能優化、系統源碼、算法及數據結構、動畫、大廠面經。



近期必讀熱門文章

最後推薦長期更新和維護的項目

  • 個人博客,將所有文章進行分類,歡迎前去查看 https://hi-dhl.com

  • KtKit 小巧而實用,用 Kotlin 語言編寫的工具庫,歡迎前去查看 KtKit

  • 計劃建立一個最全、最新的 AndroidX Jetpack 相關組件的實戰項目以及相關組件原理分析文章,正在逐漸增加 Jetpack 新成員,倉庫持續更新,歡迎前去查看 AndroidX-Jetpack-Practice

  • LeetCode / 劍指 offer / 國內外大廠面試題 / 多線程題解,語言 Java 和 kotlin,包含多種解法、解題思路、時間複雜度、空間複雜度分析

原网站

版权声明
本文为[hi-dhl]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/186/202207050929403843.html