精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

字節一面:能聊聊字節碼么?

開發 前端
棧空間是有限的,如果只有入棧沒有出棧,最后必然會出現空間不足,同時也就會報出經典的StackOverflowError(棧溢出錯誤),最常見的導致棧溢出的情況就是遞歸函數里忘了寫終止條件。

1.前言

上一篇《??你能和我聊聊Class文件么??》中,我們對Class文件的各個部分做了簡單的介紹,當時留了一個很重要的部分沒講,不是敖丙不想講啊,而是這一部分實在太重要了,不獨立成篇好好zhejinrong 講講都對不起詹姆斯·高斯林。

這最重要的部分當然就是字節碼啦。

先來個定義:Java字節碼是一組可以由Java虛擬機(JVM)執行的高度優化的指令,它被記錄在Class文件中,在虛擬機加載Class文件時執行。

說大白話就是,字節碼是Java虛擬機能夠看明白的可執行指令。

前面的文章中已經強調了很多次了,Class文件不等于字節碼,為什么我要一直強調這個事情呢?

因為在絕大部分的中文資料和博客中,這兩個東西都被嚴重的弄混了...

導致現在一說字節碼大家就會以為和Class文件是同一個東西,甚至有的文章直接把Class文件稱為“字節碼”文件。

這樣的理解顯然是有偏差的。

舉個例子,比如我們所熟知的.exe可執行文件,.exe文件中包含機器指令,但除了機器指令之外,.exe文件還包含其他與準備執行這些指令相關的信息。

因此我們不能說“機器指令”就是.exe文件,也不能把.exe文件稱為“機器指令”文件,它們只是一種包含關系,僅此而已。

同樣的,Class文件并不等于字節碼,只能說Class文件包含字節碼。

上次的文章中我們提到,字節碼(或者稱為字節碼指令)被存儲在Class文件中的方法表中,它以Code屬性的形式存在。

因此,可以通俗地說,字節碼就是Class文件方法表(methods)中的Code屬性。

今天我們來好好聊聊字節碼。

但是在講字節碼知識之前我們需要對Java虛擬機(Java Virtual Machine,簡稱JVM)的內部結構有一個簡單的理解,畢竟字節碼說到底指示虛擬機各個部分需要執行什么操作的命令,先簡單了解JVM,知己知彼方能百戰百勝。

2.JVM的內部結構

我們借這么一張圖來稍微聊聊JVM執行Class文件的流程。

這是學習JVM過程中躲不開的一張圖,當然我們今天不講那么深。

字節碼是對方法執行過程的抽象,于是我們今天只把跟方法執行過程最直接相關的幾個部分拎出來講講。

其實虛擬機執行代碼時,虛擬機中的每一部分都需要參與其中,但本篇我們更關注的是跟"執行過程"相關的幾個部分,也就是跟代碼順序執行這一動態過程相關的幾個部分。有點云里霧里了嗎,不要急,往下看。

以Hello.class作為今天的主角。

當Hello.class被加載時,首先經歷的是Class文件中的信息被加載到JVM方法區中的過程。

方法區是什么?

方法區是存儲方法運行相關信息的一個區域。

如果把Class文件中的信息理解為一顆顆的子彈,那么方法區就可以看做是成JVM的"彈藥庫",而將Class文件中的信息加載到方法區這一過程相當于“子彈上膛”。

只有當子彈上膛后,JVM才具備了“開火”的能力,這很合理吧。

例如,原本記錄在Class文件中的常量池,此時被加載到方法區中,成為運行時常量池。同時,字節碼指令也被裝配到方法區中,為方法的運行提供支持。

類加載動圖

當類Hello.class被加載到方法區后,JVM會為Hello這個類在堆上新建一個類對象。

第二個知識點來咯:堆是 放置對象實例的地方,所有的對象實例以及數組都應當在運行時分配在堆上。

一般在執行新建對象相關操作時(例如 new HashMap),才會在堆上生成對象。

但是你看,我們明明還沒開始執行代碼呢,這才剛處于類的加載階段,堆上就開始進行對象分配了,難道有什么特殊的對象實例在類加載的時候就被創建了嗎?

沒錯,這個實例的確特殊,它就是我們在反射時常常會用到的 java.lang.Class對象!!!

如果你忘了什么是反射的話,我來提醒你一下:

Hello obj = new Hello();
Class<?> clz = obj.getClass();

在Hello這個類的Class文件被加載到方法區的之后,JVM就在堆區為這個新加載的Hello類建立了一個java.lang.Class實例。

說到這里,你對”Java是一門面向對象的語言“這句話有沒有更深入的理解——在Java中,即使連類也是作為對象而存在的。

不僅如此,由于JDK 7之后,類的靜態變量存放在該類對應的java.lang.Class對象中。因此當 java.lang.Class在堆上分配好之后,靜態變量也將被分配空間,并獲得最初的零值。

注意,這里的零值指的不是靜態變量初始化哦,僅僅只是在類對象空間分配后,JVM為所有的靜態變量賦了一個用于占位的零值,零值很好理解嘛,也就是數值對象被設為0,引用類型被設為null。

到這里為止,類的信息已經完全準備好了,接下來要開始的,就是執行方法。我們在《Java代碼編譯流程是怎樣的》一文中討論過,方法是類的構造方法,它的作用是初試化類中所有的靜態變量并執行用static {}包裹的代碼塊,而且該方法的收集是有順序的:

  • 父類靜態變量初始化 及 父類靜態代碼塊;
  • 子類靜態變量初始化 及 子類靜態代碼塊。

<clinit>方法相當于是把靜態的代碼打包在一起執行,而且函數是在編譯時就已經將這些與類相關的初始化代碼按順序收集在一起了,因此在Class文件中可以看到函數:

當然,如果類中既沒有靜態變量,也沒有靜態代碼塊,則不會有函數。

總之,如果函數存在,那么在類被加載到JVM之后,函數開始執行,初始化靜態變量。

接下來我們今天最重要的部分要登場了!!!

就決定是你了,虛擬機棧!!

第三個知識點:虛擬機棧是線程中的方法的內存模型。

上面這句話聽著很抽象是吧,沒事,我來好好解釋一下。

首先要明白的是,虛擬機棧,顧名思義是用棧結構實現的一種的線性表,其限制是僅允許在表的同一端進行插入和刪除運算,這一端被稱為棧頂,相對地,把另一端稱為棧底。

棧的特性是每次操作都是從棧頂進或者從棧頂出,且滿足先進后出的順序,而虛擬機棧也繼承了這一優良傳統。

虛擬機棧是與方法執行最直接相關的一個區域,用于記錄Java方法調用的“活動記錄”(activation record)。

虛擬機棧以棧幀(frame)為單位線程的運行狀態,每調用一個方法就會分配一個新的棧幀壓入Java棧上,每從一個方法返回則彈出并撤銷相應的棧幀。

例如,這么一段代碼:

public class Hello {
public static int a = 0;
public static void main(String[] args) {
add(1,2);
}

public static int add(int x,int y) {
int z = x+y;
System.out.println(z);
return z;
}
}

它的調用鏈如下:

調用鏈

現在你明白了吧,代碼中層層調用的概念在JVM里是使用棧數據結構來實現的,調用方法時生成棧幀并入棧,方法執行完出棧,直到所有方法都出棧了,就意味著整個調用鏈結束。

還記得二叉樹的前序遍歷怎么寫的嗎:

public void preOrderTraverse(TreeNode root) {
if (root != null) {
System.out.print(root.val + "->");
preOrderTraverse(root.left);
preOrderTraverse(root.right);
}
}

這種遞歸形式本質上就是利用虛擬機棧對同一個方法的遞歸入棧實現的,如果我們寫成非遞歸形式的前序遍歷,應該是這樣子的:

public void preOrderTraverse(TreeNode root) {
// 自己聲明一個棧
Stack<TreeNode> stack = new Stack<>();
TreeNode node = root;
while (node != null || !stack.empty()) {
if (node != null) {
System.out.print(node.val + "->");
stack.push(node);
node = node.left;
} else {
TreeNode tem = stack.pop();
node = tem.right;
}
}
}

二叉樹遍歷的非遞歸形式就是由我們自己把棧寫好,并實現出棧入棧的功能,跟遞歸方式調用的本質是相似的,只不過遞歸操作中我們依賴虛擬機棧來執行入棧出棧。

總之,靠棧可以很好地表達方法間的這種層層調用的層級關系。

當然,棧空間是有限的,如果只有入棧沒有出棧,最后必然會出現空間不足,同時也就會報出經典的StackOverflowError(棧溢出錯誤),最常見的導致棧溢出的情況就是遞歸函數里忘了寫終止條件。

其次,多個線程的方法執行應當為獨立且互不干擾的,因此每一個線程都擁有自己獨立的一個虛擬機棧。

這也導致了各個線程之間方法的執行速度并不能保持一致,有時A線程先執行完,有時B線程先執行完,究其原因就是因為虛擬機棧是線程私有,各自獨立執行。

談完了虛擬機棧的整體情況,我們再來看看虛擬機棧中的棧幀。

棧幀是虛擬機棧中的基礎元素,它隨著方法的調用而創建,記錄了被調用方法的運行需要的重要信息,并隨著方法的結束而消亡。

那么你就要問了,棧幀里到底包裹了些什么東西呀?

好的同學,等我把這個問題回答完,今天的知識你至少就懂了一半。

3.棧幀的組成

棧幀主要由以下幾個部分組成:

  • 局部變量表
  • 操作數棧
  • 動態連接
  • 方法出口
  • 其他信息

3.1 局部變量表

局部變量表(Local Variable Table)是一個用于存儲方法參數和方法內部定義的局部變量的空間。

一個重要的特性是,在Java代碼被編譯為Class文件時,就已經確定了該方法所需要分配的局部變量表的最大容量。

也就是說,早在代碼編譯階段,就已經把局部變量表需要分配的大小計算好了,并記錄在Class文件中,例如:

public class Hello {
public static void main(String[] args) {
for (int i=0;i<3;i++){
System.out.printf(i+"");
}
}
}

這個類的main方法,通過javap之后可以得到其中的局部變量表:

LocalVariableTable:
Start Length Slot Name Signature
2 41 1 i I
0 44 0 args [Ljava/lang/String;

這個意思就是告訴你,這個方法會產生兩個局部變量,Slot代表他們在局部變量表中的下標。

難道方法里定義了多少個局部變量,局部變量表就會分配多少個Slot坑位嗎?

不不不,編譯器精明地很,它會采取一種稱為Slot復用的方法來節省空間,舉個例子,我們為前面的方法再增加一個for循環:

public class Hello {
public static void main(String[] args) {
for (int i=0;i<3;i++){
System.out.printf(i+"");
}
for (int j=0;j<3;j++){
System.out.printf(j+"");
}
}
}

然后會得到如下局部變量表:

LocalVariableTable:
Start Length Slot Name Signature
2 41 1 i I
45 41 1 j I
0 87 0 args [Ljava/lang/String;

雖然還是三個變量,但是i和j的Slot是同一個,也就是說,他們共用了同一個下標,在局部變量表中占的是同一個坑位。

至于原因呢,相信聰明的你已經看出來了,跟局部變量的作用域有關系。

變量i作用域是第一個for循環的內部,而當變量j創建時,i的生命周期就已經結束了。因此j可以復用i的Slot將其覆蓋掉,以此來節省空間。

所以,雖然看起來創建了三個局部變量,但其實只需要分配兩個變量的空間。

3.2 操作數棧

棧幀中的第二個重要部分是操作數棧。

等等,這怎么又來了個棧,擱這套娃呢???

沒辦法呀,棧這玩意實在太好用了,首先棧的基本操作非常簡單,只有入棧和出棧兩種,這個優勢可以保證每一條JVM的指令都代碼緊湊且體積小;其次棧用來求值也是非常經典的用法,簡單又方便喔。

也有一種基于寄存器的體系結構,將局部變量表與操作數棧的功能組合在一起,關于這兩種體系優劣勢的詳細討論可以移步至R大的博客:https://www.iteye.com/blog/rednaxelafx-492667

至于用棧來求值這種用法,大家在《數據結構》課上學棧這一結構的時候應該都接觸過了,這里不多展開。如果沒有印象了,建議看看Leetcode上的這一題:https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/

總之,情況就是這么個情況,虛擬機棧的每一個棧幀里都包含著一個操作數棧,作用是保存求值的中間結果和調用別的方法的參數等。

3.3 動態連接

動態連接這個名詞在全網的JVM中文資料中解釋得非常混亂,在你基礎沒有打牢之前不建議你深入去細究,腦子會亂掉的。

我這里會給大家一個非常通俗易懂的解釋,了解即可。

首先,棧幀中的這個動態連接,英文是Dynamic Linking,Linking在這里是作為名詞存在的,跟前面的表、棧是同一個層次的東西。

這個連接說白了就是棧幀的當前方法指向運行時常量池的一個引用。

為什么需要有這個引用呢?

前面說了,Class文件中關鍵信息都保存在方法區中,所以方法執行的時候生成的棧幀得知道自己執行的是哪個方法,靠的就是這個動態連接直接引用了方法區中該方法的實際內存位置,然后再根據這個引用,讀取其中的字節碼指令。

至于"動態"二字,牽扯到的就是Java的繼承和多態的機制,有的類繼承了其他的類并重寫了父類中的方法,因此在運行時,需要"動態地"識別應該要連接的實際的類、以及需要執行的具體的方法是哪一個。

3.4 方法出口

當一個方法開始執行,只有兩種方式退出這個方法,第一種方式是正常返回,即遇到了return語句,另一種方式則是在執行中遇到了異常,需要向上拋出。

無論是那種形式的返回,在此方法退出之后,虛擬機棧都應該退回到該方法被上層方法調用時的位置。

棧幀中的方法出口記錄的就是被調用的方法退出后應該回到上層方法的什么位置。

好了,到這里為止,棧幀中的內容就介紹結束了,接下來我們用一個簡單的例子來了解字節碼指令,以及執行執行時JVM各區域的運行過程。

4.實例:++i與i++的字節碼實例

public class Hello {
public static int a = 0;
public static void main(String[] args) {
int b = 0;
b = b++;
System.out.println(b);
b = ++b;
System.out.println(b);
a = a++;
System.out.println(a);
a = ++a;
System.out.println(a);
}
}

這段程序的輸出會是是這樣的:

0
1
0
1

這是初學Java時一道經典的誤導題,大家可能已經知其然,一眼就能看出正確的結果,可對于最底層的原理卻未必知其所以然。

b=b++執行完后變量b并沒有發生變化,只有在b=++b時變量b才自增成功。

這里其實涉及到自增操作在字節碼層面的實現問題。

我們先來看看這一段代碼對應的字節碼是怎樣的,使用jclasslib來查看Hello類的main方法中的Code屬性:

將Code中的信息粘貼出來:

 0 iconst_0
1 istore_1
2 iload_1
3 iinc 1 by 1
6 istore_1
7 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
10 iload_1
11 invokevirtual #3 <java/io/PrintStream.println : (I)V>
14 iinc 1 by 1
17 iload_1
18 istore_1
19 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
22 iload_1
23 invokevirtual #3 <java/io/PrintStream.println : (I)V>
26 getstatic #4 <com/cc/demo/Hello.a : I>
29 dup
30 iconst_1
31 iadd
32 putstatic #4 <com/cc/demo/Hello.a : I>
35 putstatic #4 <com/cc/demo/Hello.a : I>
38 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
41 getstatic #4 <com/cc/demo/Hello.a : I>
44 invokevirtual #3 <java/io/PrintStream.println : (I)V>
47 getstatic #4 <com/cc/demo/Hello.a : I>
50 iconst_1
51 iadd
52 dup
53 putstatic #4 <com/cc/demo/Hello.a : I>
56 putstatic #4 <com/cc/demo/Hello.a : I>
59 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
62 getstatic #4 <com/cc/demo/Hello.a : I>
65 invokevirtual #3 <java/io/PrintStream.println : (I)V>
68 return

Emmm....看起來有點密密麻麻,不知道該從哪看起。

其實閱讀字節碼指令是有技巧的,字節碼和源碼的對應關系已經記錄在了字節碼中,也就是Code屬性中的LineNumberTable,這里記錄的是源碼的行號和字節碼行號的對應關系。

如圖,右側的起始PC指的是字節碼的起始行號,行號則是字節碼對應的源碼行號。

將這個例子中的源碼和字節碼對應起來的效果如圖所示:

這么一對應,是不是就清晰很多了?

掌握了這個技巧之后我們就可以開始分析整體的流程和細節了。

4.1 靜態變量賦值

首先來捋一捋,當Hello類加載到JVM之后發生了什么,按我們前面說的,加載完成之后,虛擬機棧需要進行方法入棧,而眾所周知,main方法是執行的入口,所以main方法最先入棧。

但是,是這樣的嗎?

別忘記了這一行代碼:

靜態變量的賦值需要在main方法之前執行,前面已經提到了,靜態變量的賦值操作被封裝在方法中。

因此,**方法需要先于main方法入棧執行**,在本例中,方法長這樣:

當然,方法的LineNumberTable也記錄了字節碼跟源碼的對應關系,只不過在這里對應源碼只有一行:

因此public static int a = 0;這一行源代碼就對應了三行的字節碼:

0 iconst_0
1 putstatic #4 <com/cc/demo/Hello.a : I>
4 return

簡直沒有比這更適合作為字節碼教學入門素材的了!

接下來就可以開始愉快地手撕字節碼了。

第一句iconst_0,在官方的JVM規范中是這么解釋的:“Push the int constant onto the operand stack”,也就是說iconst操作是把一個int類型的常量數據壓入到操作數棧的棧頂。

這個指令開頭的字母表示的是類型,在本例中i代表int。我們可以舉一反三,當然還會有lconst代表把long類型的常量入棧到棧頂,有fconst指令表示把float類型的常量推到棧頂等等等等。

這個指令結尾的數字就是需要入棧的值了~

恭喜你,看完上面這段話,你至少已經學會了n種字節碼指令了。

不就是排列組合嘛,so easy!

再來看第二句,putstatic #4,光看字面意思就能很容易的猜出它的作用,這個指令的含義是:當前操作數棧頂出棧,并給靜態字段賦值。

把剛才放到操作數棧頂的0拿出來,賦值給常量池中#4位置字面量表示的靜態變量,這里可以看到#4位置的字面量就是。

所以,這第二行字節碼,本質上是一個賦值操作,將0這個值賦給了靜態變量a。

靜態變量存儲在堆中該類對應的Class對象實例中,也就是我們在反射機制中用對應類名拿到的那個Class對象實例。

最后一行是一個return,這個沒啥好說的。

好了,這就是本例中的方法中的全部了,并不難吧。

當<clinit>方法執行完出棧后,main方法入棧,開始執行main方法Code屬性中的字節碼指令。

為了方便講解,接下來我會逐行將源碼與其對應的字節碼貼在一起。

4.2 局部變量賦值

首先是源碼中的第六行 ,也就是main函數的第一句:

//Source code
int b = 0;

//Byte code
0 iconst_0
1 istore_1

這一句源碼對應了兩行字節碼。

其中,iconst_0這個在前面已經講過了,將int類型的常量從棧頂壓入,由于此時操作數棧為空,所以0被壓入后理所當然地既是棧頂,也是棧底。

然后是istore_1命令,這個跟iconst_0的結構很像,以一個類型縮寫開頭,以一個數字結尾,那么我們只要弄清楚store的含義就行了,store表示將棧頂的對應類型元素出棧,并保存到局部變量表指定位置中。

由于此時的棧頂元素就是剛才壓入的int類型的0,所以我們要存儲到局部變量表中的就是這個0。

那么問題來了,這個值需要放到局部變量表中的哪個位置呢?

在iconst_0命令中,末尾的數字代表需要入棧的常量,但在istore_1命令中,操作數是從操作數棧中取出的,是不用聲明的,那istore_1命令末尾這個數字的用途是什么呢?

前面說了,store表示將棧頂的對應類型元素保存到局部變量表指定位置中。

因此iconst_0指令末尾這個數字代表就是指定位置啦,也就是局部變量表的下標。

從LocalVariableTable中可以看出,下標為1的位置中存儲的就是局部變量b。

下標0位置存儲的是方法的入參。

總之,istore_1這個命令就意味著棧頂的int元素出棧,并保存到局部變量表下標為1的位置中。

同樣的,stroe這個命令也可以與各種類型縮寫的開頭組合成不同的命令,像什么lstroe、fstore等等。

ok,這又是一個經典的聲明和賦值操作。

4.3 局部變量

自增4.3.1 i++過程

我們繼續往下看,源碼第七行和它對應的字節碼:

//Source code
b = b++;

//Byte code
2 iload_1
3 iinc 1 by 1
6 istore_1

首先是iload_1命令,這個命令是與istore_1命令對應的反向命令。

store不是從操作數棧棧頂取數存到局部變量表中嘛,那么load要做的事情恰恰相反,它做的是從局部變量表指定位置中取數值,并壓入到操作數棧的棧頂。

那么iload_1詳細來說就是:從局部變量表的位置1中取出int類型的值,并壓入操作數棧。

但是,這里的取值操作其實是一個“拷貝”操作:從局部變量表中取出一個數,其實是將該值復制一份,然后壓入操作數棧,而局部變量表中的數值還保存著,沒有消失。

然后是一個iinc 1 by 1指令,這是一個雙參數指令,主要的功能是將局部變量表中的值自增一個常量值。

iinc指令的第一個參數值的含義是局部變量表下標,第二個參數值需要增加的常量值。

因此**iinc 1 by 1就表示局部變量表中下標為1位置的值增加1。**

再來看第三條指令istore_1,這個很熟悉了,操作數棧棧頂元素出棧,存到局部變量表中下標為1的位置。

等等,是不是有什么奇怪的事情發生了。

iinc 1 by 1就表示局部變量表中下標為1位置的值由0變成了1,但是istore_1把一開始從局部變量表下標1復制到操作數棧的0值又賦值到了下標位置1。

因此無論中間局部變量表中的對應元素做了什么操作,到了這一步都直接白費功夫,相當于是脫褲子放屁了。

來個動圖,看得更清晰:

局部變量b++流程

因此b = b++從字節碼上來看,自增后又被初始值覆蓋了,最終自增失敗。

繼續看下一句:

//Source code
System.out.println(b);

//Byte code
7 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
10 iload_1
11 invokevirtual #3 <java/io/PrintStream.println : (I)V>

這一句是與控制臺打印有關的字節碼,與今天的主題聯系不大,稍微過一下即可。

getstatic #2是獲取常量池中索引為#2的字面量對應的靜態元素。

iload_1 從局部變量表中索引為1的位置取數值,并壓入到操作數棧的棧頂,這里取的就是變量b的值啦。

然后最后一句是invokevirtual #3,invoke這個單詞我們在代理模式中也經常見到,是調用的意思,因此invokevirtual #3代表的就是 調用常量池索引為3的字面量對應的方法,這里的對應方法就是java/io/PrintStream.println,

最終,將變量b的值打印出來。

4.3.2 ++i過程

再來看看++b操作:

//Source code
b = ++b;

//Byte code
14 iinc 1 by 1
17 iload_1
18 istore_1

這里的三行字節碼與前面講解的b=b++中的字節碼完全一樣,只是順序發生了變化:

先在局部變量表中自增(iinc 1 by 1),然后再入棧到操作數棧中(iload_1),最后出棧保存到局部變量表中(istore_1)。

先自增就保證了自增操作是有效的,不管后面怎么折騰,參與的都是已經自增后的值,來個動圖:

4.4 靜態變量自增

最后我們看看靜態變量a的自增操作:


//Source code
a = a++;

//Byte code
26 getstatic #4 <com/cc/demo/Hello.a : I>
29 dup
30 iconst_1
31 iadd
32 putstatic #4 <com/cc/demo/Hello.a : I>
35 putstatic #4 <com/cc/demo/Hello.a : I>

getstatic #4?就是獲取常量池中索引為#4的字面量對應的靜態字段。前面已經講過了,這一步是到堆中去拿的,拿到靜態變量的值以后,會放到當前棧幀的操作數棧。

然后執行dup操作,dup是duplicate的縮寫,意思是復制。

dup指令的意義就是復制頂部操作數堆棧值并壓入棧中,也就是說此時的棧頂有兩個一模一樣的元素。

這是個什么操作啊,兩份一樣的值能干什么,別急,我們繼續往下看。

隨后是一個iconst_1,將int類型的數值1壓入棧頂。

然后是一個iadd指令,這個指令是將操作數棧棧頂的兩個int類型元素彈出并進行加法運算,最后將求得的結果壓入棧中。

像這種兩個值進行數值運算的操作,其實是操作數棧中除了簡單的入棧出棧外最常見的操作了。

類似的還有isub——棧頂兩個值相減后結果入棧,imul——棧頂兩個值相乘后結果入棧等等。

總之,此時的棧頂最上面的兩個元素是剛剛壓入棧的常量1以及靜態變量a的值0(這是剛才dup之后壓入棧的那個),這兩數一加,結果入棧,那還是個1。

接下來的指令是一個 putstatic #4,取棧頂元素出棧并賦值給靜態變量,這里當然就是靜態變量a啦。

因此靜態變量a的值就自增完成,變成了1。

可是!!!

事情到這里還沒結束,因為字節碼中清清楚楚地記錄著隨后又進行了一次 putstatic #4操作。

此時的棧頂元素就是最開始從堆中取過來的變量a的初始值0,現在把這個值出棧,又賦值給了a,這不是中間的操作都白費了嗎?

靜態變量a的值又變成0了。

等等,這一波脫褲子放屁的操作怎么似曾相識?

前面局部變量b = b++好像也經歷過這么一個過程,先復制一份自己到操作數棧中,然后局部變量表里的值一頓操作,最后操作數棧中的原始值又跑回去把自己給覆蓋了。

靜態變量不遠萬里從堆中趕到操作數棧,先復制一份自己造了個分身到操作數棧棧頂,隨后對這個棧頂的分身一頓操作,最后留在操作數棧中的原始值又跑回去把自己給覆蓋了。

難道說,這波復制操作是因為靜態變量需要分配一個位置充當局部變量表的作用,另一個位置需要充當操作數棧位置的作用?

為了驗證這個猜測是否正確,我們最后來看看a = ++a:

//Source code
a = ++a;

//Byte code
47 getstatic #4 <com/cc/demo/Hello.a : I>
50 iconst_1
51 iadd
52 dup
53 putstatic #4 <com/cc/demo/Hello.a : I>
56 putstatic #4 <com/cc/demo/Hello.a : I>

相信大家閱讀這一段字節碼已經沒有問題了,我只講講中間幾句最重要的:

靜態變量a從堆中被復制到操作數棧之后,緊跟的是一個iconst_1,將int類型的數值1壓入棧頂。

然后是一個iadd指令,將操作數棧棧頂的兩個int類型元素彈出并進行加法運算,也就是剛剛壓入棧的常量1以及靜態變量a的值0進行求和操作。

這兩數一加,結果入棧,那就是個1。

接下來有意思了,進行了一次dup操作,那操作數棧中的棧頂此時就有兩個1了。

這跟執行++b時,局部變量先在局部變量表中自增,再復制一份到操作數棧的操作是不是很像?

然后是兩個 putstatic #4,取棧頂元素出棧并賦值給靜態變量,現在棧頂兩個都是1,即使賦值兩次,最終靜態變量a的值還得是1啦。

懂了嗎寶,一切的源頭就是因為靜態變量被加載到棧幀后不能加入局部變量表,因此它將自己的一個分身壓到棧頂,現在操作數棧中有兩個一模一樣的值,一個充當局部變量表的作用,另一個充當正常操作數棧位置的作用。

5.小結

俗話說,授人以魚不如授人以漁。本文通過對虛擬機結構的簡單介紹,慢慢引申到字節碼的執行的過程。

最后用兩個例子一步一步手撕字節碼,跟著這個思路思考,相信大家以后遇到字節碼的問題也能稍微有點頭緒了吧。

責任編輯:武曉燕 來源: 敖丙
相關推薦

2024-09-19 08:51:01

HTTP解密截取

2022-01-05 21:54:51

網絡分層系統

2022-08-13 12:07:14

URLHTTP加密

2024-11-26 08:52:34

SQL優化Kafka

2022-10-10 08:13:16

遞歸通用代碼

2022-05-10 22:00:41

UDPTCP協議

2025-09-03 10:01:05

2022-06-01 11:52:42

網站客戶端網絡

2022-08-18 17:44:25

HTTPS協議漏洞

2024-11-11 10:34:55

2022-11-30 17:13:05

MySQLDynamic存儲

2022-10-19 14:08:42

SYNTCP報文

2022-01-11 20:43:16

TCPIP模型

2024-09-04 15:17:23

2022-12-02 13:49:41

2024-10-31 08:50:14

2022-07-26 00:00:02

TCPUDPMAC

2024-10-15 10:59:18

Spring MVCJava開發

2022-09-05 14:36:26

服務端TCP連接

2022-12-13 18:09:25

連接狀態客戶端
點贊
收藏

51CTO技術棧公眾號

亚洲国产色一区| 美女视频免费一区| 日韩高清中文字幕| 美女黄色片视频| 秋霞午夜在线观看| 国产mv日韩mv欧美| 777午夜精品福利在线观看| 久久久亚洲av波多野结衣| 日韩精品一页| 偷窥少妇高潮呻吟av久久免费| 欧美一区二区三区精美影视| 国产精品无码在线播放| 亚洲欧洲日本一区二区三区| 一本色道久久88精品综合| 小早川怜子一区二区三区| 天堂av中文在线观看| 国产精品美女久久久久高潮| 国产精品久久久久久久久久久久冷| 伊人中文字幕在线观看| 自拍日韩欧美| 中文字幕欧美亚洲| 在线天堂www在线国语对白| jizz欧美| 日韩欧美在线观看视频| 黄色一级视频播放| 国产露出视频在线观看| 成人久久视频在线观看| 国产精品丝袜一区二区三区| 日韩欧美国产亚洲| 欧美激情无毛| 最近中文字幕2019免费| 鲁大师私人影院在线观看| 9999精品免费视频| 在线观看国产91| 欧美久久久久久久久久久久久| 男人影院在线观看| 国产欧美一区二区精品性色| 国产精品一区二区不卡视频| 国产三级精品在线观看| 日韩av中文在线观看| 91国内产香蕉| 国产午夜精品一区二区理论影院| 亚洲欧洲美洲一区二区三区| 中国人与牲禽动交精品| 一区二区精品免费| 婷婷成人在线| 亚洲精品一区二区三区不| 久久久久亚洲av无码专区首jn| 亚洲精品一区av| 欧洲一区在线电影| 一本色道无码道dvd在线观看| 成人爽a毛片免费啪啪动漫| **欧美大码日韩| 翔田千里亚洲一二三区| 成人在线观看免费| 久久这里只有精品首页| 久久久久久久有限公司| 性感美女一级片| 99久久精品免费| 黑人巨大精品欧美一区二区小视频 | 日韩一区网站| 在线播放日韩导航| 欧美午夜精品理论片| 美女视频一区| 91精品一区二区三区在线观看| 波多野结衣xxxx| 青草综合视频| 日韩三级精品电影久久久| 国产老头和老头xxxx×| 中文字幕久久精品一区二区| 欧美成人aa大片| 国产精品九九视频| 综合综合综合综合综合网| 亚洲人精选亚洲人成在线| 精品人妻中文无码av在线| 色男人天堂综合再现| 久久天天躁狠狠躁夜夜躁| 国产一级黄色av| 国产一级一区二区| 国产日韩在线播放| www久久久久久| 91小视频在线| 亚洲高清在线观看一区| 亚洲国产精品精华素| 午夜精品福利一区二区三区av | 91视频成人免费| 91探花在线观看| 色婷婷国产精品| 日本国产一级片| 成人资源在线| 在线观看精品自拍私拍| 欧美久久久久久久久久久久| 免费在线日韩av| 国产伊人精品在线| 神马久久久久久久久久| 中文字幕一区二区三中文字幕| 男人天堂a在线| 精品免费av一区二区三区| 日韩一区二区在线看| 日本丰满少妇裸体自慰| 日韩三级在线| 久久久伊人日本| 中文字幕xxxx| 国产一区中文字幕| 精品视频一区二区| 在线观看完整版免费| 亚洲综合一二区| 精品中文字幕av| 欧美少妇激情| 精品日产卡一卡二卡麻豆| 右手影院亚洲欧美| 小说区亚洲自拍另类图片专区| 欧美精品videosex牲欧美| 天码人妻一区二区三区在线看| 看片网站欧美日韩| 精品国产一区二区三区四区精华| 国产私拍精品| 五月综合激情婷婷六月色窝| 亚洲国产精品三区| 久草在线综合| 久久精品久久久久久国产 免费| 日操夜操天天操| 捆绑变态av一区二区三区| 国产欧美日韩伦理| av在线free| 在线观看欧美黄色| 一起草在线视频| 婷婷伊人综合| 国产精品av电影| 天堂av资源网| 国产亚洲一本大道中文在线| 欧美日韩精品在线一区二区| 国产一区二区高清在线| 中文字幕最新精品| 自拍偷拍校园春色| 韩国女主播成人在线| 亚洲7777| 九九热线视频只有这里最精品| 亚洲国产精品久久精品怡红院| 免费视频网站www| 免费观看久久久4p| 都市激情久久久久久久久久久| 在线观看精品一区二区三区| 亚洲成av人片在线| 亚洲AV无码久久精品国产一区| 成人午夜av| 日产日韩在线亚洲欧美| 久蕉在线视频| 福利二区91精品bt7086| 污片免费在线观看| 日韩午夜免费| 国产精品久久一区二区三区| 日韩在线观看www| 欧美日韩精品一区二区三区四区 | 日本最新中文字幕| 免费在线观看视频一区| 日本不卡高清视频一区| 蜜桃在线视频| 日韩精品极品视频| 久久国产黄色片| 99久久国产综合精品色伊| 久久视频免费在线| 荡女精品导航| 亚洲**2019国产| 手机在线精品视频| 激情久久av一区av二区av三区| 扒开伸进免费视频| 99成人在线| 久久精彩视频| 欧美国产日韩电影| 综合av色偷偷网| 一级片视频播放| 亚洲码国产岛国毛片在线| 亚洲视频在线不卡| 欧美色一级片| 欧美日韩一区二区三区在线观看免| 亚洲优女在线| 亚洲毛片在线免费观看| 91porny九色| 国产精品嫩草99a| 999在线观看| 91精品精品| ts人妖另类在线| 国产拍在线视频| 亚洲日本成人女熟在线观看| 中国老头性行为xxxx| √…a在线天堂一区| 在线免费播放av| 日韩国产在线一| 免费成人进口网站| 麻豆一区一区三区四区| 日韩暖暖在线视频| 国产日本在线视频| 日韩欧美高清dvd碟片| 国产成人无码一区二区三区在线| 久久天天做天天爱综合色| 性刺激的欧美三级视频| 欧美福利一区| 精品91免费| 外国成人毛片| 国产综合在线看| 成年在线观看免费人视频| 8x8x8国产精品| 日韩人妻无码一区二区三区99 | 伊人在我在线看导航| 亚洲国产精品va在线看黑人动漫| 97超碰人人草| 亚洲v中文字幕| 日本黄色激情视频| 国产盗摄一区二区三区| 欧美精品一区二区三区免费播放| 日韩在线观看一区 | 那种视频在线观看| 91精品蜜臀一区二区三区在线| 免费国产一区二区| 日韩高清在线观看一区二区| 日韩美女视频免费看| 羞羞网站在线免费观看| 亚洲午夜女主播在线直播| 亚洲av无码国产精品永久一区| 欧美视频一区二区三区在线观看| 久久免费少妇高潮99精品| 国产日韩精品一区| www.四虎精品| 久久99在线观看| 黄色片网址在线观看| 欧美精品三级| 一区二区精品免费视频| 欧美性生活一级片| 91黄色精品| 久久福利在线| 国产精品视频成人| 中文字幕乱码中文乱码51精品| 精品中文字幕在线观看| 成年人在线观看| 亚洲精品视频在线观看视频| www.久久综合| 欧美欧美欧美欧美| 最近中文字幕免费观看| 欧美日韩亚洲激情| 日本少妇裸体做爰| 亚洲同性gay激情无套| 来吧亚洲综合网| 国产亚洲欧美激情| 狠狠人妻久久久久久综合蜜桃| 成人av电影在线| 制服丝袜在线第一页| 美女视频黄免费的久久| 中文字幕精品一区二区三区在线| 日本伊人午夜精品| 欧美一级黄色影院| 日本成人在线一区| xxxx一级片| 天堂一区二区在线免费观看| 男人操女人逼免费视频| 免费永久网站黄欧美| 免费观看日韩毛片| 午夜综合激情| 欧美a在线视频| 美女精品网站| 特级丰满少妇一级| 日本免费新一区视频| 国产无色aaa| 国产中文一区二区三区| 91九色蝌蚪porny| 成人高清免费观看| 天堂久久久久久| 久久在线免费观看| 丝袜美腿中文字幕| 国产精品热久久久久夜色精品三区 | 在线观看一区二区三区三州| 亚洲xxx拳头交| 精品无码国产一区二区三区av| 18成人免费观看视频| 久久这里只有精品8| 亚洲网站视频| 欧美深夜福利视频| 久久久成人网| 午夜久久福利视频| 9i在线看片成人免费| 在线免费观看成年人视频| 久久精品亚洲一区二区三区浴池 | 午夜视频在线网站| jizz一区二区| 久久久久久成人网| 一区二区三区丝袜| 亚洲久久在线观看| 884aa四虎影成人精品一区| 免费a视频在线观看| 亚洲女人天堂网| 免费观看在线黄色网| 韩国v欧美v日本v亚洲| 色豆豆成人网| 97伦理在线四区| 欧美精品尤物在线观看| 精品无码av无码免费专区| 99热免费精品| 亚洲最大成人在线观看| 国产老女人精品毛片久久| 亚洲成年人av| 中文字幕不卡的av| 久久精品99国产精| 欧洲国内综合视频| www.看毛片| www国产精品com| 成入视频在线观看| 国产日韩精品入口| 久久久久观看| 超碰人人爱人人| 久久精品麻豆| 国产chinesehd精品露脸| 久久青草欧美一区二区三区| 久久久精品视频在线| 在线亚洲欧美专区二区| www.四虎在线观看| 亚洲少妇激情视频| 2024最新电影在线免费观看| 国产精品视频白浆免费视频| 加勒比久久高清| 亚洲精品一区国产精品| 中日韩男男gay无套| 久久久久99人妻一区二区三区| 国产区在线观看成人精品| 久久久久久免费观看| 欧美日韩国产免费一区二区| 电影在线高清| 97在线视频免费| 国产欧美视频在线| 日韩精品在在线一区二区中文| 亚洲黄色一区| 91成人在线观看喷潮蘑菇| 国产精品网站在线观看| 特级西西444www高清大视频| 亚洲精品电影网| 少女频道在线观看免费播放电视剧| 国产精品男人爽免费视频1| 澳门成人av| 麻豆视频传媒入口| 国产精品 欧美精品| jizz18女人高潮| 欧美伊人久久久久久久久影院 | 亚洲欧美日韩直播| 最新av在线播放| 91夜夜揉人人捏人人添红杏| 日韩亚洲一区在线| 美女黄色片视频| 国产精品免费av| 中文人妻熟女乱又乱精品| 亚洲欧美在线免费观看| 黄污视频在线观看| 高清视频一区| 狠狠爱www人成狠狠爱综合网| 四虎成人在线播放| 中文字幕日韩精品一区| 中文字幕理论片| 中文国产成人精品| 99riav视频一区二区| 亚洲一区二区三区在线观看视频| 日本sm残虐另类| 国产99在线 | 亚洲| 欧美色网一区二区| 国产在线更新| 18成人在线| 激情一区二区| aa片在线观看视频在线播放| 欧美日韩裸体免费视频| 天天干天天插天天操| 7m第一福利500精品视频| 国产乱码精品一区二区三区四区| 99精品免费在线观看| 久久伊人中文字幕| 91精品国产乱码在线观看| 亚洲欧美日韩在线高清直播| 欧美日韩尤物久久| 一区二区三区视频| 国内国产精品久久| 九九视频免费在线观看| 亚洲国产精品va| 精品3atv在线视频| 国产手机视频在线观看| 粉嫩13p一区二区三区| 91看片在线播放| www日韩欧美| 网站一区二区| 波多野结衣家庭教师在线播放| 成人激情综合网站| 中文字幕在线观看欧美| 久久av在线看| 都市激情亚洲| 天堂一区在线观看| 亚洲一区免费在线观看| 亚洲欧美色视频| 国产精品嫩草视频| 一本久道综合久久精品| 日本一二三不卡视频| 日韩亚洲欧美高清| 亚洲成av在线| 国产一区二区四区| 国产三级精品在线| 天天操天天射天天|