成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Java 進階之字節碼剖析

開發 后端
從今天起我打算整一個 Java 系列的進階基礎文章,萬丈高樓平地起,打好基礎我們才能走得更好,舉個例子,之前我在武哥的 Kafka 文章中看到這樣的一句話「除此之外,頁緩存(pageCache)還有一個巨大的優勢。

[[439668]]

前言

你好,我是坤哥。

從今天起我打算整一個 Java 系列的進階基礎文章,萬丈高樓平地起,打好基礎我們才能走得更好,舉個例子,之前我在武哥的 Kafka 文章中看到這樣的一句話「除此之外,頁緩存(pageCache)還有一個巨大的優勢。用過 Java 的人都知道:如果不用頁緩存,而是用 JVM 進程中的緩存,對象的內存開銷非常大(通常是真實數據大小的幾倍甚至更多)」,如果你不了解 Java 對象的表示,看到這樣的話會一臉懵逼:對象的開銷到底有多巨大,反過來看,如果你掌握了 Java 中的對象布局,GC,NIO 等原理,理解這些框架的原理及其設計思路就不是什么難事。

另一個讓我下決心寫這個系列的原因是經常有一些讀者問一些學習路線的事,之前我寫過一些大綱,但沒有從點的層面展開,所以這次準備從點的思路來將各個知識點細細道來,然后再整理成 pdf,這樣之后如果有人再問起,直接把這個 pdf 扔給他們就完事了 ^_^

每個系列都會以圖文并茂的方式來講解,做到深入淺出,舉個例子,上面我們說了對象的開銷很大,到底有多大呢,我會用圖解的方式來帶你一步步分析,看完后相信你會明白為什么 int[128][2] ,int[256] 這兩個數組看起來一樣,但實際上前者比后者多了 246% 的額外開銷,再比如我們都知道 Eden 區或 tenured(老年代區)滿了會觸發 yong gc 或 old gc,不過導致 gc 停頓時間過長的原因其實有挺多的,如果你看完我總結的這些通用的思路,相信你就能根據這套理論來快速地排查問題了,這個系列干貨很多,相信對提升大家的 Java 內功有不少幫助,記得得文末點贊支持一下哦 ^_^

Java 系列大綱如下:

本篇我們先來學習下字節碼 ,畢竟這是 Java 能跨平臺的根本原因,而且通過了解字節碼也可以徹底揭開 JVM 運行程序的秘密,整體會用問答的形式來講解。

能否簡單介紹一下 Java 的特性

Java 是一門面向對象,靜態類型的語言,具有跨平臺的特點,與 C,C++ 這些需要手動管理內存,編譯型的語言不同,它是解釋型的,具有跨平臺和自動垃圾回收的特點,那么它的跨平臺到底是怎么實現的呢?

我們知道計算機只能識別二進制代碼表示的機器語言,所以不管用的什么高級語言,最終都得翻譯成機器語言才能被 CPU 識別并執行,對于 C++這些編譯型語言來說是直接一步到位轉為相應平臺的可執行文件(即機器語言指令),而對 Java 來說,則首先由編譯器將源文件編譯成字節碼,再在運行時由虛擬機(JVM)解釋成機器指令來執行,我們可以看下下圖:

也就是說 Java 的跨平臺其實是通過先生成字節碼,再由針對各個平臺實現的 JVM 來解釋執行實現的,JVM 屏蔽了 OS 的差異,我們知道 Java 工程都是以 Jar 包分發(一堆 class 文件的集合體)部署的,這就意味著 jar 包可以在各個平臺上運行(由相應平臺的 JVM 解釋執行即可),這就是 Java 能實現跨平臺的原因所在。

這也是為什么 JVM 能運行 Scala、Groovy、Kotlin 這些語言的原因,并不是 JVM 直接來執行這些語言,而是這些語言最終都會生成符合 JVM 規范的字節碼再由 JVM 執行,不知你是否注意到,使用字節碼也利用了計算機科學中的分層理念,通過加入字節碼這樣的中間層,有效屏蔽了與上層的交互差異。

JVM 是怎么執行字節碼的

在此之前我們先來看下 JVM 的整體內存結構,對其有一個宏觀的認識,然后再來看 JVM 是如何執行字節碼的。

JVM 內存結構

JVM 在內存中主要分為「棧」,「堆」,「非堆」以及 JVM 自身,堆主要用來分配類實例和數組,非堆包括「方法區」、「JVM內部處理或優化所需的內存(如JIT編譯后的代碼緩存)」、每個類結構(如運行時常數池、字段和方法數據)以及方法和構造方法的代碼。

我們主要關注棧,我們知道線程是 cpu 調度的最小單位,在 JVM 中一旦創建一個線程,就會為其分配一個線程棧,線程會調用一個個方法,每個方法都會對應一個個的棧幀壓到線程棧里,JVM 中的棧內存結構如下:

JVM 棧內存結構

至此我們總算接近 JVM 執行的真相了,JVM 是以棧幀為單位執行的,棧幀由以下四個部分組成:

  • 返回值
  • 局部變量表(Local Variables):存儲方法用到的本地變量
  • 動態鏈接:在字節碼中,所有的變量和方法都是以符號引用的形式保存在 class 文件的常量池中的,比如一個方法調用另外的方法,是通過常量池中指向方法的符號引用來表示的,動態鏈接的作用就是為了將這些符號引用轉換為調用方法的直接引用,這么說可能有人還是不理解,所以我們先執行一下 javap -verbose Demo.class命令來查看一下字節碼中的常量池是咋樣的

注意:以上只列出了常量池中的部分符號引用

可以看到 Object 的 init 方法是由 #4.#16 表示的,而 #4 又指向了 #19,#19 表示 Object,#16 又指向了 #7.#8,#7 指向了方法名,#8 指向了 ()V(表示方法的返回值為 void,且無方法參數),字節碼加載后,會把類信息加載到元空間(Java 8 以后)中的方法區中,動態鏈接會把這些符號引用替換為調用方法的直接引用,如下圖示:

那為什么要提供動態鏈接呢,通過上面這種方式繞了好幾個彎才定位到具體的執行方法,效率不是低了很多嗎,其實主要是為了支持 Java 的多態,比如我們聲明一個 Father f = new Son()這樣的變量,但執行 f.method() 的時候會綁定到 son 的 method(如果有的話),這就是用到了動態鏈接的技術,在運行時才能定位到具體該調用哪個方法,動態鏈接也稱為后期綁定,與之相對的是靜態鏈接(也稱為前期綁定),即在編譯期和運行期對象的方法都保持不變,靜態鏈接發生在編譯期,也就是說在程序執行前方法就已經被綁定,java 當中的方法只有final、static、private和構造方法是前期綁定的。而動態鏈接發生在運行時,幾乎所有的方法都是運行時綁定的。

舉個例子來看看兩者的區別,一目了解。

  1. class Animal{ 
  2.     public void eat(){ 
  3.         System.out.println("動物進食"); 
  4.     } 
  5.  
  6. class Cat extends Animal{ 
  7.     @Override 
  8.     public void eat() { 
  9.         super.eat();//表現為早期綁定(靜態鏈接) 
  10.         System.out.println("貓進食"); 
  11.     } 
  12. public class AnimalTest { 
  13.     public void showAnimal(Animal animal){ 
  14.         animal.eat();//表現為晚期綁定(動態鏈接) 
  15.     } 
  • 操作數棧(Operand Stack):程序主要由指令和操作數組成,指令用來說明這條操作做什么,比如是做加法還是乘法,操作數就是指令要執行的數據,那么指令怎么獲取數據呢,指令集的架構模型分為基于棧的指令集架構和基于寄存器的指令集架構兩種,JVM 中的指令集屬于前者,也就是說任何操作都是用棧來管理,基于棧指令可以更好地實現跨平臺,棧都是是在內存中分配的,而寄存器往往和硬件掛鉤,不同的硬件架構是不一樣的,不利于跨平臺,當然基于棧的指令集架構缺點也很明顯,基于棧的實現需要更多指令才能完成(因為棧只是一個FILO結構,需要頻繁壓棧出棧),而寄存器是在CPU的高速緩存區,相較而言,基于棧的速度要慢不少,這也是為了跨平臺而做出的一點性能犧牲,畢竟魚和熊掌不可兼得。

Java 字節碼技術簡介

注意線程中還有一個「PC 程序計數器」,是每個線程獨有的,記錄著當前線程所執行的字節碼的行號指示器,也就是指向下一條指令的地址,也就是將執行的指令代碼。由執行引擎讀取下一條指令。我們先來看下看一下字節碼長啥樣。假設我們有以下 Java 代碼:

  1. package com.mahai; 
  2. public class Demo { 
  3.       private  int a = 1; 
  4.     public static void foo() { 
  5.         int a = 1; 
  6.         int b = 2; 
  7.         int c = (a + b) * 5; 
  8.     } 

執行 javac Demo.java 后可以看到其字節碼如下:

字節碼是給 JVM 看的,所以我們需要將其翻譯成人能看懂的代碼,好在 JDK 提供了反解析工具 javap ,可以根據字節碼反解析出 code 區(匯編指令)、本地變量表、異常表和代碼行偏移量映射表、常量池等信息。我們執行以下命令來看下根據字節碼反解析的文件長啥樣(更詳細的信息可以執行 javap -verbose 命令,在本例中我們重點關注 Code 區是如何執行的,所以使用了 javap -c 來執行。

  1. javap -c Demo.class 

轉換成這種形式可讀性強了很多,那么aload_0,invokespecial 這些表示什么含義呢, javap 是怎么根據字節碼來解析出這些指令出來的呢!

首先我們需要明白什么是指令,指令=操作碼+操作數,操作碼表示這條指令要做什么,比如加減乘除,操作數即操作碼操作的數,比如 1+ 2 這條指令,操作碼其實是加法,1,2 為操作數,在 Java 中每個操作碼都由一個字節表示,每個操作碼都有對應類似 aload_0,invokespecial,iconst_1 這樣的助記符,有些操作碼本來就包含著操作數,比如字節碼 0x04 對應的助記符為 iconst_1, 表示 將 int 型 1 推送至棧頂,這些操作碼就相當于指令,而有些操作碼需要配合操作數才能形成指令,如字節碼 0x10 表示 bipush,后面需要跟著一個操作數,表示 將單字節的常量值(-128~127)推送至棧頂。以下為列出的幾個字節碼與助記符示例:

字節碼 助記符 表示含義
0x04 iconst_1 將int型1推送至棧頂
0xb7 invokespecial 調用超類構建方法, 實例初始化方法, 私有方法
0x1a iload_0 將第一個int型本地變量推送至棧頂
0x10 bipush 將單字節的常量值(-128~127)推送至棧頂

至此我們不難明白 javap 的作用了,它主要就是找到字節碼對應的的助記符然后再展示在我們面前的,我們簡單看下上述的默認構造方法是如何根據字節碼映射成助記符并最終呈現在我們面前的:

最左邊的數字是 Code 區中每個字節的偏移量,這個是保存在 PC 的程序計數中的,比如如果當前指令指向 1,下一條就指向 4。

另外大家不難發現,在源碼中其實我們并沒有定義默認構造函數,但在字節碼中卻生成了,而且你會發現我們在源碼中定義了private int a = 1;但這個變量賦值的操作卻是在構造方法中執行的(下文會分析到),這就是理解字節碼的意義:它可以反映 JVM 執行程序的真正邏輯,而源碼只是表象,要深入分析還得看字節碼!

接下來我們就來瞧一瞧構造方法對應的指令是如何執行的,首先我們來看一下在 JVM 中指令是怎么執行的。

  • 首先 JVM 會為每個方法分配對應的局部變量表,可以認為它是一個數組,每個坑位(我們稱為 slot)為方法中分配的變量,如果是實例方法,這些局部變量可以是 this, 方法參數,方法里分配的局部變量,這些局部變量的類型即我們熟知的 int,long 等八大基本,還有引用,返回地址,每個 slot 為 4 個字節,所以像 Long , Double 這種 8 個字節的要占用 2 個 slot, 如果這個方法為實例方法,則第一個 slot 為 this 指針, 如果是靜態方法則沒有 this 指針。
  • 分配好局部變量表后,方法里如果涉及到賦值,加減乘除等操作,那么這些指令的運算就需要依賴于操作數棧了,將這些指令對應的操作數通過壓棧,彈棧來完成指令的執行。

比如有 int i = 69 這樣的指令,對應的字碼節指令如下:

  1. 0:bipush 69 
  2. 2:istore_0 

其在內存中的操作過程如下:

可以看到主要分兩步:第一步首先把 69 這個 int 值壓棧,然后再彈棧,把 69 彈出放到局部變量表 i 對應的位置,istore_0 表示彈棧,將其從操作數棧中彈出整型數字存儲到本地變量中,0 表示本地變量在局部變量表的第 0 個 slot。

理解了上面這個操作,我們再來看一下默認構造函數對應的字節碼指令是如何執行的:

首先我們需要先來理解一下上面幾個指令:

  • aload_0:從局部變量表中加載第 0 個 slot 中的對象引用到操作數棧的棧頂,這里的 0 表示第 0 個位置,也就是 this。
  • invokespecial:用來調用構造函數,但也可以用于調用同一個類中的 private 方法, 以及 可見的超類方法,在此例中表示調用父類的構造器(因為 #1 符號引用指向對應的 init 方法)。
  • iconst_1:將 int 型 1推送至棧頂。
  • putfield:它接受一個操作數,這個操作數引用的是運行時常量池里的一個字段,在這里這個字段是 a。賦給這個字段的值,以及包含這個字段的對象引用,在執行這條指令的時候,都會從操作數棧頂上 pop 出來。前面的 aload_0 指令已經把包含這個字段的對象(this)壓到操作數棧上了,而后面的 iconst_1 又把 1 壓到棧里。最后 putfield 指令會將這兩個值從棧頂彈出。執行完的結果就是這個對象的 a 這個字段的值更新成了 1。

接下來我們來詳細解釋以上以上助記符代表的含義:

  • 第一條命令 aload_0,表示從局部變量表中加載第 0 個 slot 中的對象引用到操作數棧的棧頂,也就是將 this 加載到棧頂,如下:

  • 第二步 invokespecial #1,表示彈棧并且執行 #1 對應的方法,#1 代表的含義可以從旁邊的解釋(# Method java/lang/Object."":()V)看出,即調用父類的初始化方法,這也印證了那句話:子類初始化時會從初始化父類
  • 之后的命令 aload_0,iconst_1,putfied #2 圖解如下:

可能有人有些奇怪,上述 6: putfield #2命令中的 #2 怎么就代表 Demo 的私有成員 a 了,這就涉及到字節碼中的常量池概念了,我們執行 javap -verbose path/Demo.class 可以看到這些字面量代表的含義,#1,#2 這種數字形式的表示形式也被稱為符號引用,程序運行期會將符號引用轉換為直接引用。

由此可知 #2 代表 Demo 類的 a 屬性,如下:

從最終的葉子節點可以看出 #2 最終代表的是 Demo 類中類型為 int(I 代表 int 代表 int 類型),名稱為 a 的變量。

我們再來用動圖看一下 foo 的執行流程,相信你現在能理解其含義了。

唯一需要注意的此例中的 foo 是個靜態方法,所以局部變量區是沒有 this 的。

相信你不難發現 JVM 執行字節碼的流程與 CPU 執行機器碼步驟如出一轍,都經歷了「取指令」,「譯碼」,「執行」,「存儲計算結果」這四步,首先程序計數器指向下一條要執行的指令,然后 JVM 獲取指令,由本地執行引擎將字節碼操作數轉成機器碼(譯碼)執行,執行后將值存儲到局部變量區(存儲計算結果)中。

最后關于字節碼我推薦兩款工具:

  • 一個是 Hex Fiend,一款很好的十六進制編輯器,可以用來查看編輯字節碼
  • 一款是 Intellij Idea 的插件 jclasslib Bytecode viewer,能為你展示 javap -verbose 命令對應的常量池,接口, Code 等數據,非常的直觀,對于分析字節碼非常有幫忙,如下:

本文轉載自微信公眾號「碼海」,可以通過以下二維碼關注。轉載本文請聯系碼海公眾號。

 

責任編輯:武曉燕 來源: 碼海
相關推薦

2024-10-29 10:54:07

2011-12-01 14:56:30

Java字節碼

2010-09-25 10:20:05

JAVA字節碼

2019-10-30 08:45:21

JS代碼NodeJS

2023-07-03 08:11:48

java字節碼字段

2022-03-30 10:10:17

字節碼棧空間

2019-12-20 12:38:28

Java技術工具

2012-01-12 09:20:49

Java

2023-08-30 11:03:47

Java工具

2018-04-04 15:05:17

虛擬機字節碼引擎

2012-03-28 10:30:33

ScalaJava

2021-09-12 07:30:10

配置

2023-03-27 16:44:23

2022-03-09 09:00:41

SwiftUI視圖生成器Swift

2010-08-27 13:07:00

CSS規則

2011-06-10 11:05:05

Qt Quick QML

2009-06-30 16:46:45

Criteria進階查

2022-03-01 09:01:56

SwiftUI動畫進階Canvas

2021-05-28 23:04:23

Python利器執行

2010-09-25 10:32:52

Java字節碼
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲 91| 久久久av中文字幕 | 国产精品久久久久久久久久久免费看 | 亚洲精品久久久 | 日韩在线观看一区 | 国内精品视频在线观看 | 日韩国产欧美在线观看 | 久久人人爽人人爽 | 国产日韩欧美在线观看 | 一区在线播放 | 国产成人综合网 | 亚洲精品久久久久久首妖 | 日本免费一区二区三区 | 一区视频在线 | 天天碰夜夜操 | 免费一区二区在线观看 | 密乳av | 亚洲欧美综合 | 久久精品国产亚洲 | 黄色一级大片在线免费看产 | 羞羞的视频免费观看 | 2一3sex性hd| 国产乱码精品一区二区三区忘忧草 | 超碰97免费在线 | 亚洲精品久久久久国产 | 国产一级片91 | 一区二区不卡高清 | 久久国 | 亚洲精品乱码久久久久久黑人 | 91精品国产综合久久婷婷香蕉 | 日韩高清一区 | 国内精品一区二区三区 | 久久久国产精品一区 | 亚洲国产精品va在线看黑人 | 7777久久| 欧美www在线 | 精品国产欧美 | 成人精品一区二区三区 | 欧美日韩在线视频一区二区 | 国产精品亚洲片在线播放 | 天久久|