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

攜程面試官竟然問我 Java 虛擬機(jī)棧!

云計(jì)算 虛擬化
從《JVM 內(nèi)存區(qū)域劃分》這篇文章中,大家應(yīng)該 get 到了,Java 虛擬機(jī)內(nèi)存區(qū)域可以劃分為程序計(jì)數(shù)器、Java 虛擬機(jī)棧、本地方法棧和堆。今天,我們來圍繞其中的一個(gè)區(qū)域——Java 虛擬機(jī)棧,深入地展開下。

[[393190]]

大家好,我是那個(gè)永遠(yuǎn) 18 歲的老妖怪~噓

從《JVM 內(nèi)存區(qū)域劃分》這篇文章中,大家應(yīng)該 get 到了,Java 虛擬機(jī)內(nèi)存區(qū)域可以劃分為程序計(jì)數(shù)器、Java 虛擬機(jī)棧、本地方法棧和堆。今天,我們來圍繞其中的一個(gè)區(qū)域——Java 虛擬機(jī)棧,深入地展開下。

先說明一下哈。這篇文章的標(biāo)題里帶了一個(gè)“攜程面試官”,有標(biāo)題黨的嫌疑。但有一說一,確實(shí)有讀者在上一篇文章里留言說,攜程面試官問他了 Java 虛擬機(jī)內(nèi)存方面的知識(shí)點(diǎn),所以今天的標(biāo)題我就“借題發(fā)揮”了。

從“相見恨晚”這個(gè)詞中,我估摸著這名讀者在這道面試題前面折戟沉沙了。這么說吧,面試官確實(shí)喜歡問 Java 虛擬機(jī)方面的知識(shí)點(diǎn),因?yàn)楹苣芸疾斐鲆幻麘?yīng)聘者的真實(shí)功底,所以我打算多寫幾篇這方面的文章,希望能給大家多一點(diǎn)點(diǎn)幫助~

Java 虛擬機(jī)以方法作為基本的執(zhí)行單元,“棧幀(Stack Frame)”則是用于支持 Java 虛擬機(jī)進(jìn)行方法調(diào)用和方法執(zhí)行的基本數(shù)據(jù)結(jié)構(gòu)。每一個(gè)棧幀中都包含了局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法返回地址和一些額外的附加信息(比如與調(diào)試、性能手機(jī)相關(guān)的信息)。之前的文章里有提到過這些概念,并做了一些簡(jiǎn)單扼要的介紹,但我覺得還不夠詳細(xì),所以這篇重點(diǎn)要來介紹一下棧幀中的這些概念。

1)局部變量表

局部變量表(Local Variables Table)用來保存方法中的局部變量,以及方法參數(shù)。當(dāng) Java 源代碼文件被編譯成 class 文件的時(shí)候,局部變量表的最大容量就已經(jīng)確定了。

我們來看這樣一段代碼。

  1. public class LocalVaraiablesTable { 
  2.     private void write(int age) { 
  3.         String name = "沉默王二"
  4.     } 

write() 方法有一個(gè)參數(shù) age,一個(gè)局部變量 name。

然后用 Intellij IDEA 的 jclasslib 查看一下編譯后的字節(jié)碼文件 LocalVaraiablesTable.class。可以看到 write() 方法的 Code 屬性中,Maximum local variables(局部變量表的最大容量)的值為 3。

按理說,局部變量表的最大容量應(yīng)該為 2 才對(duì),一個(gè) age,一個(gè) name,為什么是 3 呢?

當(dāng)一個(gè)成員方法(非靜態(tài)方法)被調(diào)用時(shí),第 0 個(gè)變量其實(shí)是調(diào)用這個(gè)成員方法的對(duì)象引用,也就是那個(gè)大名鼎鼎的 this。調(diào)用方法 write(18),實(shí)際上是調(diào)用 write(this, 18)。

點(diǎn)開 Code 屬性,查看 LocalVaraiableTable 就可以看到詳細(xì)的信息了。

第 0 個(gè)是 this,類型為 LocalVaraiablesTable 對(duì)象;第 1 個(gè)是方法參數(shù) age,類型為整形 int;第 2 個(gè)是方法內(nèi)部的局部變量 name,類型為字符串 String。

當(dāng)然了,局部變量表的大小并不是方法中所有局部變量的數(shù)量之和,它與變量的類型和變量的作用域有關(guān)。當(dāng)一個(gè)局部變量的作用域結(jié)束了,它占用的局部變量表中的位置就被接下來的局部變量取代了。

來看下面這段代碼。

  1. public static void method() { 
  2.     // ① 
  3.     if (true) { 
  4.         // ② 
  5.         String name = "沉默王二"
  6.     } 
  7.     // ③ 
  8.     if(true) { 
  9.         // ④ 
  10.         int age = 18; 
  11.     } 
  12.     // ⑤ 
  • method() 方法的局部變量表大小為 1,因?yàn)槭庆o態(tài)方法,所以不需要添加 this 作為局部變量表的第一個(gè)元素;
  • ②的時(shí)候局部變量有一個(gè) name,局部變量表的大小變?yōu)?1;
  • ③的時(shí)候 name 變量的作用域結(jié)束;
  • ④的時(shí)候局部變量有一個(gè) age,局部變量表的大小為 1;
  • ⑤的時(shí)候局 age 變量的作用域結(jié)束;

關(guān)于局部變量的作用域,《Effective Java》 中的第 57 條建議:

將局部變量的作用域最小化,可以增強(qiáng)代碼的可讀性和可維護(hù)性,并降低出錯(cuò)的可能性。

在此,我還有一點(diǎn)要提醒大家。為了盡可能節(jié)省棧幀耗用的內(nèi)存空間,局部變量表中的槽是可以重用的,就像 method() 方法演示的那樣,這就意味著,合理的作用域有助于提高程序的性能。

局部變量表的容量以槽(slot)為最小單位,一個(gè)槽可以容納一個(gè) 32 位的數(shù)據(jù)類型(比如說 int,當(dāng)然了,《Java 虛擬機(jī)規(guī)范》中沒有明確指出一個(gè)槽應(yīng)該占用的內(nèi)存空間大小,但我認(rèn)為這樣更容易理解),像 float 和 double 這種明確占用 64 位的數(shù)據(jù)類型會(huì)占用兩個(gè)緊挨著的槽。

來看下面的代碼。

  1. public void solt() { 
  2.     double d = 1.0; 
  3.     int i = 1; 

用 jclasslib 可以查看到,solt() 方法的 Maximum local variables 的值為 4。

為什么等于 4 呢?帶上 this 也就 3 個(gè)呀?

查看 LocalVaraiableTable 就明白了,變量 i 的下標(biāo)為 3,也就意味著變量 d 占了兩個(gè)槽。

2)操作數(shù)棧

同局部變量表一樣,操作數(shù)棧(Operand Stack)的最大深度也在編譯的時(shí)候就確定了,被寫入到了 Code 屬性的 maximum stack size 中。當(dāng)一個(gè)方法剛開始執(zhí)行的時(shí)候,操作數(shù)棧是空的,在方法執(zhí)行過程中,會(huì)有各種字節(jié)碼指令往操作數(shù)棧中寫入和取出數(shù)據(jù),也就是入棧和出棧操作。

來看下面這段代碼。

  1. public class OperandStack { 
  2.     public void test() { 
  3.         add(1,2); 
  4.     } 
  5.  
  6.     private int add(int a, int b) { 
  7.         return a + b; 
  8.     } 

OperandStack 類共有 2 個(gè)方法,test() 方法中調(diào)用了 add() 方法,傳遞了 2 個(gè)參數(shù)。用 jclasslib 可以看到,test() 方法的 maximum stack size 的值為 3。

這是因?yàn)檎{(diào)用成員方法的時(shí)候會(huì)將 this 和所有參數(shù)壓入棧中,調(diào)用完畢后 this 和參數(shù)都會(huì)一一出棧。通過 「Bytecode」 面板可以查看到對(duì)應(yīng)的字節(jié)碼指令。

aload_0 用于將局部變量表中下標(biāo)為 0 的引用類型的變量,也就是 this 加載到操作數(shù)棧中;

  • iconst_1 用于將整數(shù) 1 加載到操作數(shù)棧中;
  • iconst_2 用于將整數(shù) 2 加載到操作數(shù)棧中;
  • invokevirtual 用于調(diào)用對(duì)象的成員方法;
  • pop 用于將棧頂?shù)闹党鰲?
  • return 為 void 方法的返回指令。

再來看一下 add() 方法的字節(jié)碼指令。

  • iload_1 用于將局部變量表中下標(biāo)為 1 的 int 類型變量加載到操作數(shù)棧上(下標(biāo)為 0 的是 this);
  • iload_2 用于將局部變量表中下標(biāo)為 2 的 int 類型變量加載到操作數(shù)棧上;
  • iadd 用于 int 類型的加法運(yùn)算;
  • ireturn 為返回值為 int 的方法返回指令。

操作數(shù)中的數(shù)據(jù)類型必須與字節(jié)碼指令匹配,以上面的 iadd 指令為例,該指令只能用于整形數(shù)據(jù)的加法運(yùn)算,它在執(zhí)行的時(shí)候,棧頂?shù)膬蓚€(gè)數(shù)據(jù)必須是 int 類型的,不能出現(xiàn)一個(gè) long 型和一個(gè) double 型的數(shù)據(jù)進(jìn)行 iadd 命令相加的情況。

3)動(dòng)態(tài)鏈接

每個(gè)棧幀都包含了一個(gè)指向運(yùn)行時(shí)常量池中該棧幀所屬方法的引用,持有這個(gè)引用是為了支持方法調(diào)用過程中的動(dòng)態(tài)鏈接(Dynamic Linking)。

來看下面這段代碼。

  1. public class DynamicLinking { 
  2.     static abstract class Human { 
  3.        protected abstract void sayHello(); 
  4.     } 
  5.      
  6.     static class Man extends Human { 
  7.         @Override 
  8.         protected void sayHello() { 
  9.             System.out.println("男人哭吧哭吧不是罪"); 
  10.         } 
  11.     } 
  12.      
  13.     static class Woman extends Human { 
  14.         @Override 
  15.         protected void sayHello() { 
  16.             System.out.println("山下的女人是老虎"); 
  17.         } 
  18.     } 
  19.  
  20.     public static void main(String[] args) { 
  21.         Human man = new Man(); 
  22.         Human woman = new Woman(); 
  23.         man.sayHello(); 
  24.         woman.sayHello(); 
  25.         man = new Woman(); 
  26.         man.sayHello(); 
  27.     } 

大家對(duì) Java 重寫有了解的話,應(yīng)該能看懂這段代碼的意思。Man 類和 Woman 類繼承了 Human 類,并且重寫了 sayHello() 方法。來看一下運(yùn)行結(jié)果:

  1. 男人哭吧哭吧不是罪 
  2. 山下的女人是老虎 
  3. 山下的女人是老虎 

這個(gè)運(yùn)行結(jié)果很好理解,man 的引用類型為 Human,但指向的是 Man 對(duì)象,woman 的引用類型也為 Human,但指向的是 Woman 對(duì)象;之后,man 又指向了新的 Woman 對(duì)象。

從面向?qū)ο缶幊痰慕嵌龋瑥亩鄳B(tài)的角度,我們對(duì)運(yùn)行結(jié)果是很好理解的,但站在 Java 虛擬機(jī)的角度,它是如何判斷 man 和 woman 該調(diào)用哪個(gè)方法的呢?

用 jclasslib 看一下 main 方法的字節(jié)碼指令。

  • 第 1 行:new 指令創(chuàng)建了一個(gè) Man 對(duì)象,并將對(duì)象的內(nèi)存地址壓入棧中。
  • 第 2 行:dup 指令將棧頂?shù)闹祻?fù)制一份并壓入棧頂。因?yàn)榻酉聛淼闹噶?invokespecial 會(huì)消耗掉一個(gè)當(dāng)前類的引用,所以需要復(fù)制一份。
  • 第 3 行:invokespecial 指令用于調(diào)用構(gòu)造方法進(jìn)行初始化。
  • 第 4 行:astore_1,Java 虛擬機(jī)從棧頂彈出 Man 對(duì)象的引用,然后將其存入下標(biāo)為 1 局部變量 man 中。
  • 第 5、6、7、8 行的指令和第 1、2、3、4 行類似,不同的是 Woman 對(duì)象。
  • 第 9 行:aload_1 指令將第局部變量 man 壓入操作數(shù)棧中。
  • 第 10 行:invokevirtual 指令調(diào)用對(duì)象的成員方法 sayHello(),注意此時(shí)的對(duì)象類型為 com/itwanger/jvm/DynamicLinking$Human。
  • 第 11 行:aload_2 指令將第局部變量 woman 壓入操作數(shù)棧中。
  • 第 12 行同第 10 行。

注意,從字節(jié)碼的角度來看,man.sayHello()(第 10 行)和 woman.sayHello()(第 12 行)的字節(jié)碼是完全相同的,但我們都知道,這兩句指令最終執(zhí)行的目標(biāo)方法并不相同。

究竟發(fā)生了什么呢?

還得從 invokevirtual 這個(gè)指令著手,看它是如何實(shí)現(xiàn)多態(tài)的。根據(jù)《Java 虛擬機(jī)規(guī)范》,invokevirtual 指令在運(yùn)行時(shí)的解析過程可以分為以下幾步:

①、找到操作數(shù)棧頂?shù)脑厮赶虻膶?duì)象的實(shí)際類型,記作 C。

②、如果在類型 C 中找到與常量池中的描述符匹配的方法,則進(jìn)行訪問權(quán)限校驗(yàn),如果通過則返回這個(gè)方法的直接引用,查找結(jié)束;否則返回 java.lang.IllegalAccessError 異常。

③、否則,按照繼承關(guān)系從下往上一次對(duì) C 的各個(gè)父類進(jìn)行第二步的搜索和驗(yàn)證。

④、如果始終沒有找到合適的方法,則拋出 java.lang.AbstractMethodError 異常。

也就是說,invokevirtual 指令在第一步的時(shí)候就確定了運(yùn)行時(shí)的實(shí)際類型,所以兩次調(diào)用中的 invokevirtual 指令并不是把常量池中方法的符號(hào)引用解析到直接引用上就結(jié)束了,還會(huì)根據(jù)方法接受者的實(shí)際類型來選擇方法版本,這個(gè)過程就是 Java 重寫的本質(zhì)。我們把這種在運(yùn)行期根據(jù)實(shí)際類型確定方法執(zhí)行版本的過程稱為動(dòng)態(tài)鏈接。

4)方法返回地址

當(dāng)一個(gè)方法開始執(zhí)行后,只有兩種方式可以退出這個(gè)方法:

正常退出,可能會(huì)有返回值傳遞給上層的方法調(diào)用者,方法是否有返回值以及返回值的類型根據(jù)方法返回的指令來決定,像之前提到的 ireturn 用于返回 int 類型,return 用于 void 方法;還有其他的一些,lreturn 用于 long 型,freturn 用于 float,dreturn 用于 double,areturn 用于引用類型。

異常退出,方法在執(zhí)行的過程中遇到了異常,并且沒有得到妥善的處理,這種情況下,是不會(huì)給它的上層調(diào)用者返回任何值的。

無論是哪種方式退出,在方法退出后,都必須返回到方法最初被調(diào)用時(shí)的位置,程序才能繼續(xù)執(zhí)行。一般來說,方法正常退出的時(shí)候,PC 計(jì)數(shù)器的值會(huì)作為返回地址,棧幀中很可能會(huì)保存這個(gè)計(jì)數(shù)器的值,異常退出時(shí)則不會(huì)。

方法退出的過程實(shí)際上等同于把當(dāng)前棧幀出棧,因此接下來可能執(zhí)行的操作有:恢復(fù)上層方法的局部變量表和操作數(shù)棧,把返回值(如果有的話)壓入調(diào)用者棧幀的操作數(shù)棧中,調(diào)整 PC 計(jì)數(shù)器的值,找到下一條要執(zhí)行的指令等。

本文轉(zhuǎn)載自微信公眾號(hào)「沉默王二」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系沉默王二公眾號(hào)。

 

責(zé)任編輯:武曉燕 來源: 沉默王二
相關(guān)推薦

2023-02-09 08:48:47

Java虛擬機(jī)

2021-09-29 19:17:51

編碼URLEncodeGBK

2021-12-02 08:19:06

MVCC面試數(shù)據(jù)庫

2024-02-21 07:40:17

JVM內(nèi)存虛擬機(jī)

2020-04-16 08:22:11

HTTPS加解密協(xié)議

2021-05-20 08:54:16

Go面向對(duì)象

2010-08-23 15:06:52

發(fā)問

2022-05-24 08:03:28

InnoDBMySQL數(shù)據(jù)

2021-06-03 08:55:54

分布式事務(wù)ACID

2020-05-20 17:35:40

JavaString面試官

2022-10-17 00:04:30

索引SQL訂單

2020-12-03 07:39:50

HashMap底層數(shù)據(jù)

2020-06-03 15:07:01

Java虛擬機(jī)棧JVM

2020-08-10 07:58:18

異步編程調(diào)用

2021-05-08 07:53:33

面試線程池系統(tǒng)

2021-05-19 08:17:35

秒殺場(chǎng)景高并發(fā)

2022-04-01 07:52:42

JavaScript防抖節(jié)流

2021-03-11 08:51:00

存儲(chǔ)面試位置

2021-08-28 09:06:11

Dubbo架構(gòu)服務(wù)

2023-07-27 06:59:30

Native線程數(shù)據(jù)結(jié)構(gòu)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 中文字幕日韩一区 | 国产一二三区在线 | 久草网址 | 日韩黄色免费 | 性色综合 | 免费观看成人鲁鲁鲁鲁鲁视频 | 夜夜干夜夜操 | 国产欧美一区二区三区另类精品 | 国产99久久精品一区二区永久免费 | a级黄色毛片免费播放视频 国产精品视频在线观看 | 亚洲一区网站 | 一区二区三区精品 | 国产精品成人一区二区三区 | 国产99久久精品一区二区永久免费 | a级免费观看视频 | 日本三级黄视频 | 日本不卡一区 | 羞羞的视频免费看 | 99久久电影 | 岛国毛片在线观看 | 黄色大片免费播放 | 中文字幕在线免费观看 | 精品国产乱码久久久久久丨区2区 | 精国产品一区二区三区四季综 | 日韩精品成人 | 国产在线一区二 | 久久中文字幕一区 | 男人天堂午夜 | 亚洲91| 亚洲成人免费 | 狠狠av| 综合九九 | 精品久久久久久亚洲精品 | 亚洲精品一区二区三区 | 欧美成人一区二区三区 | 亚洲乱码一区二区三区在线观看 | 久久精品网 | 欧美一区二区三区在线观看 | 国产97在线看 | 欧美色专区 | 欧美精品久久一区 |