java tostring方法_Java虚拟机如执行方法调用的(二)?
虛方法調用
Java里所有非私有實例方法調用都會被編譯成invokevirtual指令.接口方法調用都會被編譯成invokeinterface指令.這兩種指令都屬于Java虛方法的調用.
在大多數情況下, Java虛擬機需要根據調用者的動態類型, 來確定虛方法調用的目標方法.這個過程被稱為動態綁定. 那么相對于靜態綁定的非虛方法調用來說, 虛方法調用更加耗時.
在Java虛擬機中, 靜態綁定包括:
- 調用靜態方法的invokestatic指令.
- 調用構造器、私有實例方法及超類非私有實例方法的invokespecial指令.
- 如果虛方法調用指向一個被標記為final的方法,那么Java虛擬機也可以靜態綁定該虛方法調用的目標方法.
Java虛擬機采用了一種用空間換時間的策略來實現動態綁定.
它為每個類生成一張「方法表」,有以快速定位目標方法.
方法表
在類加載準備階段, 它除了為靜態字段分配內存之外, 還會構造與類相關聯的「方法表」.這個數據結構, 是Java虛擬機實現動態綁定的關鍵.
方法表本質上是一個數組,每個數組元素指向一個當前類及其祖先類中非私有的實例方法.
這些方法可能是具體的、可執行的方法,也可能是沒有相應字節碼的「抽象方法」.「方法表」滿足兩個特性:
- 子類方法表中包含父類方法表中所有的方法.
- 子類方法在方法表中的索引值, 與它所重寫父類方法的索引值相同.
在執行過程中, Java虛擬機將獲取調用者的實際類型, 并在該實際類型的「方法表」中, 根據「索引值」獲取目標方法. 這個過程就是「動態綁定」
「示例」
// 定義一個抽象的動物類.上面簡單的示例中, 各個類的「方法表」分別是:
各個類的方法表在示例當中, 「Animal類」的方法表包括兩個方法:
- toString()
- eat()
它們分別對應0號和1號.
之所以方法表調換了toString()和eat()方法的位置, 是因為toString()方法的「索引值」需要與「Object」類中同名的方法的「索引值」一致. (為了保持簡潔, 暫時不考慮Object類中的其它方法).
「Dog」類的方法表同樣也包括兩個方法. 其中, 0號方法指向「繼承」而來的toString()方法. 1號方法則指向自己重寫的「eat」方法.
「Cat」類的方法表中包括3個方法, 除了繼承而來的「Animal」類的toString()方法, 自己重寫的「eat」方法之外, 還包括獨立的「sleep」方法.
測試代碼的執行過程可以這么理解, 可以把「Java虛擬機」理解與一個飼養員, 每當過來一個動物, 飼養員先看看是啥東西(獲取動態類型), 然后翻出狗/貓對應的飼養手冊(獲取動態類型的方法表). 手冊的第1條則顯示動物如何吃東西的方法(用1作為索引來查找方法表對應的目標方法).
實際上, 使用了「方法表」的「動態綁定」與「靜態綁定」相比. 僅僅是多出了幾個「內存 解引用操作」.訪問棧上的調用者, 獲取調用者的動態類型, 讀取該類型的方法表, 讀取該方法表中的某個索引值所對應的目標方法.相對于創建并初始化Java棧幀來說, 這幾個解引用操作簡直可以忽略不計. 動態綁定獲取目標方法「虛方法」優化的效果看上去十分美好, 但實際上僅存大于「解釋執行」中, 或者「即時編譯」代碼的最壞情況中.
這是因為「即時編譯」還擁有另外兩種性能更好的優化手段: 「內聯緩存(inlining cache)」和「方法內聯(method inlining)」. 先說第一種「內聯緩存」.
內聯緩存
「內聯緩存」是一種加快「動態綁定」的優化技術.它能夠「緩存虛方法調用中調用者的動態類型, 以及該類型所對應的目標方法」 . 在之后的執行過程中, 如果碰到已「緩存」的類型, 「內聯緩存」便會直接調用該類型所對應的「目標方法」. 如果沒有碰到已緩存的類型, 「內聯緩存」則會退化至使用「基本方法表的動態綁定」.
內聯緩存查找目標方法在針對多態的優化手段中, 通常會提及以下三個術語.
對于「內聯緩存」來說, 我們也對應的「單態內聯緩存」、「多態內聯緩存」、「超多態內聯緩存」.
「單態內存緩存」, 就是只「緩存」一種動態類型以及它所「對應的目標方法」.
實現: 比較所「緩存的動態類型」, 如果命中, 則「直接調用對應的目標方法」.「多態內聯緩存」則緩存了多個動態類型及其「目標方法」.
實現: 它需要逐個將所「緩存」的「動態類型」與「當前動態類型」進行比較, 如果命中, 則調用「對應的目標方法」.一般來說, 會將「更加熱門的動態類型」放在「前面」. 在實踐中, 大部分的「虛方法」調用均是「單態」的, 也就是只有一種「動態類型」.
為了節省內存空間,Java虛擬機只采用「單態內聯緩存」.前面所說的, 當「內聯緩存」沒有命中的情況下, Java虛擬機需要「重新使用方法表進行動態綁定」. 對于「內聯緩存」中的內容, 我們有兩種選擇.
2. 「劣化為超多態狀態」
這也是Java虛擬機的具體實現方式. 處于這種狀態下的「內聯緩存」,實際上放棄了優化的機會.它將直接訪問「方法表」, 來「動態綁定目標方法」.
與「替換內聯緩存記錄」的做法相比, 它犧牲了優化的機會, 但是「節省了寫緩存的額外開銷」.雖然「內聯緩存」隨帶「內聯」二字, 但是它并沒有「內聯目標方法」.
這里需要明確的是, 任何方法調用除非被內聯, 否則都會固定開銷.這些開銷來源于「保存程序在該方法中的執行位置、新建、壓入和彈出新方法所使用的棧 幀」. 對于極其簡單的方法而言, 比如說getter/setter, 這部分「固定開銷」占據的CPU時間甚至超過了方法本身. 此外, 在「即時編譯」中, 「方法內聯」不僅僅能夠消除方法「調用的固定開銷」, 而且還增加了進一步優化的可能性.本篇完...
謝謝觀看 ~~
總結
以上是生活随笔為你收集整理的java tostring方法_Java虚拟机如执行方法调用的(二)?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python计算precision,re
- 下一篇: 支持向量机SVM的python实现