《深入理解Java虛擬機》讀書筆記:位元組碼指令簡介

来源:https://www.cnblogs.com/fhey/archive/2023/08/17/17639064.html
-Advertisement-
Play Games

位元組碼指令簡介 Java虛擬機的指令由一個位元組長度的、代表著某種特定操作含義的數字(稱為操作碼,Opcode)以及跟隨其後的零至多個代表此操作所需參數(稱為操作數,Operands)而構成。由於Java虛擬機採用面向操作數棧而不是寄存器的架構(這兩種架構的區別和影響將在第8章中探討),所以大多數的指 ...


位元組碼指令簡介

 

  Java虛擬機的指令由一個位元組長度的、代表著某種特定操作含義的數字(稱為操作碼,Opcode)以及跟隨其後的零至多個代表此操作所需參數(稱為操作數,Operands)而構成。由於Java虛擬機採用面向操作數棧而不是寄存器的架構(這兩種架構的區別和影響將在第8章中探討),所以大多數的指令都不包含操作數,只有一個操作碼。

  位元組碼指令集是一種具有鮮明特點、優劣勢都很突出的指令集架構,由於限制了Java虛擬機操作碼的長度為一個位元組(即0~255),這意味著指令集的操作碼總數不可能超過256條;又由於Class文件格式放棄了編譯後代碼的操作數長度對齊,這就意味著虛擬機處理那些超過一個位元組數據的時候,不得不在運行時從位元組中重建出具體數據的結構.

如果要將一個16位長度的無符號整數使用兩個無符號位元組存儲起來(將它們命名為byte1和byte2),那它們的值應該是這樣的:

(byte1 << 8) | byte2

  

  這種操作在某種程度上會導致解釋執行位元組碼時損失一些性能。但這樣做的優勢也非常明顯,放棄了操作數長度對齊[插圖],就意味著可以省略很多填充和間隔符號;用一個位元組來代表操作碼,也是為了儘可能獲得短小精幹的編譯代碼。這種追求儘可能小數據量、高傳輸效率的設計是由Java語言設計之初面向網路、智能家電的技術背景所決定的,並一直沿用至今。

 

1 、位元組碼與數據類型

  在Java虛擬機的指令集中,大多數的指令都包含了其操作所對應的數據類型信息。對於大部分與數據類型相關的位元組碼指令,它們的操作碼助記符中都有特殊的字元來表明專門為哪種數據類型服務:i代表對int類型的數據操作,l代表long,s代表short,b代表byte,c代表char,f代表float,d代表double,a代表reference。也有一些指令的助記符中沒有明確地指明操作類型的字母,如arraylength指令,它沒有代表數據類型的特殊字元,但操作數永遠只能是一個數組類型的對象。還有另外一些指令,如無條件跳轉指令goto則是與數據類型無關的。

表6-31 Java虛擬機指令集所支持的數據類型

2、載入和存儲指令

  載入和存儲指令用於將數據在棧幀中的局部變數表和操作數棧(見第2章關於記憶體區域的介紹)之間來回傳輸,這類指令包括如下內容。

(1)將一個局部變數載入到操作棧:iload、iload_<n>、lload、lload_<n>、fload、fload_<n>、dload、dload_<n>、aload、aload_<n>。

(2)將一個數值從操作數棧存儲到局部變數表:istore、istore_<n>、lstore、lstore_<n>、fstore、fstore_<n>、dstore、dstore_<n>、astore、astore_<n>。

(3)將一個常量載入到操作數棧:bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_m1、iconst_<i>、lconst_<l>、fconst_<f>、dconst_<d>。

(4)擴充局部變數表的訪問索引的指令:wide。

存儲數據的操作數棧和局部變數表主要就是由載入和存儲指令進行操作,除此之外,還有少量指令,如訪問對象的欄位或數組元素的指令也會向操作數棧傳輸數據。

 

3 、運算指令

  運算或算術指令用於對兩個操作數棧上的值進行某種特定運算,並把結果重新存入到操作棧頂。大體上算術指令可以分為兩種:對整型數據進行運算的指令與對浮點型數據進行運算的指令,無論是哪種算術指令,都使用Java虛擬機的數據類型,由於沒有直接支持byte、short、char和boolean類型的算術指令,對於這類數據的運算,應使用操作int類型的指令代替。整數與浮點數的算術指令在溢出和被零除的時候也有各自不同的行為表現,所有的算術指令如下。

(1)加法指令:iadd、ladd、fadd、dadd。

(2)減法指令:isub、lsub、fsub、dsub。

(3)乘法指令:imul、lmul、fmul、dmul。

(4)除法指令:idiv、ldiv、fdiv、ddiv。

(5)求餘指令:irem、lrem、frem、drem。

(5)取反指令:ineg、lneg、fneg、dneg。

(6)位移指令:ishl、ishr、iushr、lshl、lshr、lushr。

(7)按位或指令:ior、lor。

(8)按位與指令:iand、land。

(10)按位異或指令:ixor、lxor。

(11)局部變數自增指令:iinc。

(12)比較指令:dcmpg、dcmpl、fcmpg、fcmpl、lcmp。

 

4、類型轉換指令

  可以將兩種不同的數值類型進行相互轉換,這些轉換操作一般用於實現用戶代碼中的顯式類型轉換操作,或者用來處理本節開篇所提到的位元組碼指令集中數據類型相關指令無法與數據類型一一對應的問題。Java虛擬機直接支持(即轉換時無需顯式的轉換指令)以下數值類型的寬化類型轉換(Widening Numeric Conversions,即小範圍類型向大範圍類型的安全轉換):

(1)int類型到long、float或者double類型。

(2)long類型到float、double類型。

(3)float類型到double類型。

 

  在將一個浮點值窄化轉換為整數類型T(T限於int或long類型之一)的時候,將遵循以下轉換規則:

(1)如果浮點值是NaN,那轉換結果就是int或long類型的0。

(2)如果浮點值不是無窮大的話,浮點值使用IEEE 754的向零舍入模式取整,獲得整數值v,如果v在目標類型T(int或long)的表示範圍之內,那轉換結果就是v。

(3)否則,將根據v的符號,轉換為T所能表示的最大或者最小正數。

 

5、對象創建與訪問指令

  雖然類實例和數組都是對象,但Java虛擬機對類實例和數組的創建與操作使用了不同的位元組碼指令(在第7章會講到數組和普通類的類型創建過程是不同的)。對象創建後,就可以通過對象訪問指令獲取對象實例或者數組實例中的欄位或者數組元素,這些指令如下。

(1)創建類實例的指令:new。

(2)創建數組的指令:newarray、anewarray、multianewarray。

(3)訪問類欄位(static欄位,或者稱為類變數)和實例欄位(非static欄位,或者稱為實例變數)的指令:getfield、putfield、getstatic、putstatic。

(4)把一個數組元素載入到操作數棧的指令:baload、caload、saload、iaload、laload、faload、daload、aaload。

(5)將一個操作數棧的值存儲到數組元素中的指令:bastore、castore、sastore、iastore、fastore、dastore、aastore。

(6)取數組長度的指令:arraylength。

(7)檢查類實例類型的指令:instanceof、checkcast。

 

6 、操作數棧管理指令

  如同操作一個普通數據結構中的堆棧那樣,Java虛擬機提供了一些用於直接操作操作數棧的指令,包括:

(1)將操作數棧的棧頂一個或兩個元素出棧:pop、pop2。

(2)複製棧頂一個或兩個數值並將複製值或雙份的複製值重新壓入棧頂:dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2。

(3)將棧最頂端的兩個數值互換:swap。

 

7、控制轉移指令

  控制轉移指令可以讓Java虛擬機有條件或無條件地從指定的位置指令而不是控制轉移指令的下一條指令繼續執行程式,從概念模型上理解,可以認為控制轉移指令就是在有條件或無條件地修改PC寄存器的值。控制轉移指令如下。

(1)條件分支:ifeq、iflt、ifle、ifne、ifgt、ifge、ifnull、ifnonnull、if_icmpeq、if_icmpne、if_icmplt、if_icmpgt、if_icmple、if_icmpge、if_acmpeq和if_acmpne。

(2)複合條件分支:tableswitch、lookupswitch。

(3)無條件分支:goto、goto_w、jsr、jsr_w、ret。

在Java虛擬機中有專門的指令集用來處理int和reference類型的條件分支比較操作,為了可以無須明顯標識一個實體值是否null,也有專門的指令用來檢測null值。

與前面算術運算時的規則一致,對於boolean類型、byte類型、char類型和short類型的條件分支比較操作,都是使用int類型的比較指令來完成,而對於long類型、float類型和double類型的條件分支比較操作,則會先執行相應類型的比較運算指令(dcmpg、dcmpl、fcmpg、fcmpl、lcmp,見6.4.3節),運算指令會返回一個整型值到操作數棧中,隨後再執行int類型的條件分支比較操作來完成整個分支跳轉。由於各種類型的比較最終都會轉化為int類型的比較操作,int類型比較是否方便完善就顯得尤為重要,所以Java虛擬機提供的int類型的條件分支指令是最為豐富和強大的。

 

8 、方法調用和返回指令

  方法調用(分派、執行過程)將在第8章具體講解,這裡僅列舉以下5條用於方法調用的指令。

(1)invokevirtual指令用於調用對象的實例方法,根據對象的實際類型進行分派(虛方法分派),這也是Java語言中最常見的方法分派方式。

(2)invokeinterface指令用於調用介面方法,它會在運行時搜索一個實現了這個介面方法的對象,找出適合的方法進行調用。

(3)invokespecial指令用於調用一些需要特殊處理的實例方法,包括實例初始化方法、私有方法和父類方法。

(4)invokestatic指令用於調用類方法(static方法)。

(5)invokedynamic指令用於在運行時動態解析出調用點限定符所引用的方法,並執行該方法,前面4條調用指令的分派邏輯都固化在Java虛擬機內部,而invokedynamic指令的分派邏輯是由用戶所設定的引導方法決定的。

方法調用指令與數據類型無關,而方法返回指令是根據返回值的類型區分的,包括ireturn(當返回值是boolean、byte、char、short和int類型時使用)、lreturn、freturn、dreturn和areturn,另外還有一條return指令供聲明為void的方法、實例初始化方法以及類和介面的類初始化方法使用。

 

9 、異常處理指令

  在Java程式中顯式拋出異常的操作(throw語句)都由athrow指令來實現,除了用throw語句顯式拋出異常情況之外,Java虛擬機規範還規定了許多運行時異常會在其他Java虛擬機指令檢測到異常狀況時自動拋出。例如,在前面介紹的整數運算中,當除數為零時,虛擬機會在idiv或ldiv指令中拋出ArithmeticException異常。而在Java虛擬機中,處理異常(catch語句)不是由位元組碼指令來實現的(很久之前曾經使用jsr和ret指令來實現,現在已經不用了),而是採用異常表來完成的。

 

10、同步指令

  Java虛擬機可以支持方法級的同步和方法內部一段指令序列的同步,這兩種同步結構都是使用管程(Monitor)來支持的。

方法級的同步是隱式的,即無須通過位元組碼指令來控制,它實現在方法調用和返回操作之中。虛擬機可以從方法常量池的方法表結構中的ACC_SYNCHRONIZED訪問標誌得知一個方法是否聲明為同步方法。當方法調用時,調用指令將會檢查方法的ACC_SYNCHRONIZED訪問標誌是否被設置,如果設置了,執行線程就要求先成功持有管程,然後才能執行方法,最後當方法完成(無論是正常完成還是非正常完成)時釋放管程。在方法執行期間,執行線程持有了管程,其他任何線程都無法再獲取到同一個管程。如果一個同步方法執行期間拋出了異常,並且在方法內部無法處理此異常,那麼這個同步方法所持有的管程將在異常拋到同步方法之外時自動釋放。

  同步一段指令集序列通常是由Java語言中的synchronized語句塊來表示的,Java虛擬機的指令集中有monitorenter和monitorexit兩條指令來支持synchronized關鍵字的語義,正確實現synchronized關鍵字需要Javac編譯器與Java虛擬機兩者共同協作支持,譬如如下代碼所示的代碼。

void onlyMe(Foo f){
		synchronized(f){
  			doSomeThing();
  	}
}

編譯後,這段代碼生成的位元組碼序列如下:

示例代碼編譯後的位元組碼

 

  編譯器必須確保無論方法通過何種方式完成,方法中調用過的每條monitorenter指令都必須執行其對應的monitorexit指令,而無論這個方法是正常結束還是異常結束。從上面的示例代碼的位元組碼序列中可以看到,為了保證在方法異常完成時monitorenter和monitorexit指令依然可以正確配對執行,編譯器會自動產生一個異常處理器,這個異常處理器聲明可處理所有的異常,它的目的就是用來執行monitorexit指令。

   
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ### 一、實現效果 + 點擊全選按鈕/取消全選,控制商品的全選或取消 + 每個商品的覆選框都選中後,自動勾上全選按鈕,或者商品任何一個覆選框沒有選中,取消全選 ![image](https://img2023.cnblogs.com/blog/2408012/202308/2408012-2023 ...
  • ```html 1基本標簽 你是風兒我是沙 你是風兒我是沙 你是風兒我是沙 你是風兒我是沙 你是風兒我是沙 你是風兒我是沙 床前明月光, 疑是地上霜. 舉頭望明月, 低頭思故鄉. 大家好,我是段落標簽p。我按了enter一下 換行了 大家好,我是段落標簽p。我按了enter一下 換行了 定義粗體文本 ...
  • 本文主要講述京東門詳業務在支撐過程中遇到的困境,面對問題我們在效率提升、質量保障等方向的探索和實踐,在此將實踐過程中問題解決的思路和方案與大家一起分享,也希望能給大家帶來一些新的啟發 ...
  • >我們是[袋鼠雲數棧 UED 團隊](http://ued.dtstack.cn/),致力於打造優秀的一站式數據中台產品。我們始終保持工匠精神,探索前端道路,為社區積累並傳播經驗價值。 >本文作者:[霜序](https://luckyfbb.github.io/blog) ## 前言 在[前一篇文章 ...
  • 本篇博文將深入介紹 Vue3 組合式 API 和單文件組件的寫法。我們將從安裝和配置 Vue3 開始,然後逐步詳細展示如何創建一個簡單的單文件組件。除此之外,我們還將討論使用組合式 API 的常見模式和技巧,例如響應式狀態管理、替代生命周期鉤子函數的方法、自定義組合式 API、數據的響應式處理和偵聽... ...
  • 一、背景 會員系統是一種基礎系統,跟公司所有業務線的下單主流程密切相關。如果會員系統出故障,會導致用戶無法下單,影響範圍是全公司所有業務線。所以,會員系統必須保證高性能、高可用,提供穩定、高效的基礎服務。 隨著同程和藝龍兩家公司的合併,越來越多的系統需要打通同程APP、藝龍APP、同程微信小程式、藝 ...
  • ### 歡迎訪問我的GitHub > 這裡分類和彙總了欣宸的全部原創(含配套源碼):[https://github.com/zq2599/blog_demos](https://github.com/zq2599/blog_demos) ### 本篇概覽 - 本文是《quarkus資料庫篇》系列的第 ...
  • 函數的功能是從輸入的字元串切片中去除重覆的元素,並返回去重後的結果。具體的實現邏輯如下: 創建一個空的結果切片result,用於存儲去重後的字元串。 創建一個臨時的maptempMap,用於存放不重覆的字元串。map的鍵是字元串,值是位元組類型。 遍歷輸入的字元串切片slc中的每個元素e: 首先,獲取 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...