日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

JVM-虚拟机栈详解 附面试高频题 (手画多图)!!!深入浅出,绝对值得收藏哈!!!

發(fā)布時(shí)間:2025/3/19 编程问答 18 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JVM-虚拟机栈详解 附面试高频题 (手画多图)!!!深入浅出,绝对值得收藏哈!!! 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

受多種情況的影響,又開始看JVM 方面的知識(shí)。

1、Java 實(shí)在過于內(nèi)卷,沒法不往深了學(xué)。

2、面試題問的多,被迫學(xué)習(xí)。

3、純粹的好奇。
很喜歡一句話:“八小時(shí)內(nèi)謀生活,八小時(shí)外謀發(fā)展。
— 望別日與君相見時(shí),君已有所成。
共勉

來源:b站說唱新時(shí)代中 剁椒沙丁魚隊(duì)《世界以痛吻我》中歌手:于貞

作者:博主制作

原因:人美聲甜可愛可辣可酷(我才不會(huì)說是我喜歡的) 🤸?♂?🤸?♂?🤸?♂?

JVM-虛擬機(jī)棧詳解 附面試高頻題 (手畫多圖)

    • 一、虛擬機(jī)棧概述
    • 二、棧幀
      • 2.1、棧與棧楨:
      • 2.2、棧幀概述
      • 2.3、想一想我們遇到過哪些與棧相關(guān)的異常?
      • 2.4、設(shè)置棧內(nèi)存大小
      • 2.5、局部變量表
        • 概述:
        • 局部變量的存放 Slot(變量槽)
        • 靜態(tài)變量與局部變量的對(duì)比
      • 2.6、操作數(shù)棧
      • 2.7、動(dòng)態(tài)鏈接
        • 概述
        • 鏈接
      • 2.8、方法返回地址
        • 異常表:
      • 2.9、一些附加信息
    • 面試
    • 自言自語(yǔ)

一、虛擬機(jī)棧概述

先給大家來看一下 運(yùn)行時(shí)數(shù)據(jù)區(qū)的圖示👇

如果大家沒咋了解Java的內(nèi)存結(jié)構(gòu),就常會(huì)粗粒度地將JVM中的內(nèi)存區(qū)理解為僅有Java堆(heap)和Java戰(zhàn)(stack)?為什么?🤳🧐

首先棧是運(yùn)行時(shí)的單位,而堆是存儲(chǔ)的單位

  • 棧解決程序的運(yùn)行問題,即程序如何執(zhí)行,或者說如何處理數(shù)據(jù)。
  • 堆解決的是數(shù)據(jù)存儲(chǔ)的問題,即數(shù)據(jù)怎么放,放哪里

不過今天我們討論的是虛擬機(jī)棧。堆的文章之后才更👨?💻。

虛擬機(jī)棧:java虛擬機(jī)棧是線程私有的,他與線程的聲明周期同步。虛擬機(jī)棧描述的是java方法執(zhí)行的內(nèi)存模型,每個(gè)方法執(zhí)行都會(huì)創(chuàng)建一個(gè)棧幀,棧幀包含局部變量表、操作數(shù)棧、動(dòng)態(tài)連接、方法出口等。

注意: 🏂

  • 它的執(zhí)行速度僅次于程序計(jì)數(shù)器
  • 對(duì)于棧來說不存在垃圾回收問題
  • 主管Java程序的運(yùn)行,它保存方法的局部變量、部分結(jié)果,并參與方法的調(diào)用和返回。
  • 二、棧幀

    2.1、棧與棧楨:

    每一個(gè)方法的執(zhí)行到執(zhí)行完成,對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)中從入棧到出棧的過程。 👨?🚀
    1、java虛擬機(jī)棧棧頂?shù)臈褪钱?dāng)前執(zhí)行方法的棧幀,PC寄存器會(huì)指向該地址。👇
    2、當(dāng)這個(gè)方法調(diào)用其他方法的時(shí)候就會(huì)創(chuàng)建一個(gè)新的棧幀,這個(gè)新的棧幀會(huì)被方法Java虛擬機(jī)棧的棧頂變?yōu)楫?dāng)前的活動(dòng)棧,在當(dāng)前只有當(dāng)前活動(dòng)棧的本地變量才能被使用,
    3、當(dāng)這個(gè)棧幀所有指令都完成的時(shí)候,這個(gè)棧幀被移除,之前的棧幀變?yōu)榛顒?dòng)棧,前面移除棧幀的返回值變?yōu)檫@個(gè)棧幀的一個(gè)操作數(shù)。

    2.2、棧幀概述

    棧幀(Stack Frame)是用于支持虛擬機(jī)進(jìn)行方法調(diào)用和方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu),它是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的虛擬機(jī)棧(Virtual Machine Stack)的棧元素。每個(gè)棧幀都包含了:

  • 局部變量表
  • 操作數(shù)棧(或表達(dá)式棧)
  • 動(dòng)態(tài)連接 (或指向運(yùn)行時(shí)常量池的方法引用)
  • 方法返回地址(或方法正常退出或者異常退出的定義)
  • 一些額外的附加信息
    👩?💻
    在編譯代碼的時(shí),棧幀中需要多大的局部變量表,多深的操作數(shù)棧都已經(jīng)完全確定了,并且寫入到了方法表的Code屬性中,因此一個(gè)棧幀需要分配多少內(nèi)存,不會(huì)受到程序運(yùn)行期變量數(shù)據(jù)的影響,而僅僅取決于具體虛擬機(jī)的實(shí)現(xiàn)。
  • 如下圖:

    左邊是通過javap -v 類名的.class 命令反編譯出來的。注意啊,得在生成目標(biāo)文件夾目錄下👇

    我們通過上圖可以看到,在編譯過程中,已經(jīng)給每個(gè)棧楨分配好了 操作數(shù)棧 的深度啊,局部變量表的大小等等。

    局部變量表是4的原因:雖然我們?cè)谶@個(gè)方法中只定義了a,b,c 三個(gè)局部變量,但是大家還記得 this嗎,你沒有想錯(cuò),確實(shí)在這個(gè)局部變量表中,第一個(gè)是this。更深層次解釋不了,技術(shù)不夠。😂。到局部變量部分帶大家一起看。

    在這里插一句哈,如果大家不熟悉這種命令行去反編譯的話,在這里介紹一個(gè)idea 的插件。

    放心哈,那插件作者肯定沒給我打錢,我是感覺真的挺可的,對(duì)于我們新手學(xué)習(xí)這方面。

    名字: jclasslib Bytecode Viewer

    用法

    我們將一個(gè)類編譯完后,兄弟們,編譯沒有問題吧。不行,感覺還是要貼出來哈。

    編譯完成之后。我們打開 菜單中 -->view 選項(xiàng)。

    里面的具體的東東靠大家慢慢發(fā)掘了哈,我們還是回歸正文啦。給大家個(gè)Oracle 的JVM 官方規(guī)范。方便指令的查找解釋哈。

    2.3、想一想我們遇到過哪些與棧相關(guān)的異常?

    Java 虛擬機(jī)規(guī)范允許Java棧的大小是動(dòng)態(tài)的或者是固定不變的。 🏄?♂?

    1、如果采用固定大小的Java虛擬機(jī)棧,那每一個(gè)線程的Java虛擬機(jī)棧容量可以在線程創(chuàng)建的時(shí)候獨(dú)立選定。如果線程請(qǐng)求分配的棧容量超過Java虛擬機(jī)棧允許的最大容量,Java虛擬機(jī)將會(huì)拋出一個(gè)``StackoverflowError `異常。(棧溢出)

    舉個(gè)栗子:相信大家肯定學(xué)過遞歸算法,如果它一直沒有出口,結(jié)果就是棧溢出。🚣?♂?

    2、如果Java虛擬機(jī)棧可以動(dòng)態(tài)擴(kuò)展,并且在嘗試擴(kuò)展的時(shí)候無法申請(qǐng)到足夠的內(nèi)存,或者在創(chuàng)建新的線程時(shí)沒有足夠的內(nèi)存去創(chuàng)建對(duì)應(yīng)的虛擬機(jī)棧,那Java虛擬機(jī)將會(huì)拋出一個(gè) OutOfMemoryError 異常。(也就是內(nèi)存溢出異常OOM)

    2.4、設(shè)置棧內(nèi)存大小

    剛剛大家也看到了,我們可以使用參數(shù) -Xss選項(xiàng)來設(shè)置線程的最大棧空間,棧的大小直接決定了函數(shù)調(diào)用的最大可達(dá)深度。

    -Xss1m // m 是Mb -Xss1k // k 是Kb

    2.5、局部變量表

    概述:

    局部變量表:Local Variables,被稱之為局部變量數(shù)組或本地變量表。定義為一個(gè)數(shù)字?jǐn)?shù)組,主要用于存儲(chǔ)方法參數(shù)和定義在方法體內(nèi)的局部變量這些數(shù)據(jù)類型包括各類基本數(shù)據(jù)類型、對(duì)象引用(reference),以及returnAddress類型。
    局部變量表是線程私有的。

    局部變量表所需的容量大小是在編譯期確定下來的,并保存在方法的Code屬性的maximum local variables數(shù)據(jù)項(xiàng)中。在方法運(yùn)行期間是不會(huì)改變局部變量表的大小的。

    局部變量表中的變量只在當(dāng)前方法調(diào)用中有效。在方法執(zhí)行時(shí),虛擬機(jī)通過使用局部變量表完成參數(shù)值到參數(shù)變量列表的傳遞過程。當(dāng)方法調(diào)用結(jié)束后,隨著方法棧幀的銷毀,局部變量表也會(huì)隨之銷毀。

    👨?💻👨?🚀🤹?♂?🤽?♂?🏌??♂?🐱?🚀🐱?🐉🎊🧬🚀🛫🚢🛸🚤?🌈🌊
    悄咪咪試一波小表情,課間休息會(huì),怕看疲憊了,就放棄繼續(xù)看下去啦哈。🤗

    局部變量的存放 Slot(變量槽)

    參數(shù)值的存放總是在局部變量數(shù)組的index0開始,到數(shù)組長(zhǎng)度-1的索引結(jié)束。🤭

    局部變量表,最基本的存儲(chǔ)單元是Slot(變量槽)局部變量表中存放編譯期可知的各種基本數(shù)據(jù)類型(8種),引用類型(reference),returnAddress類型的變量。

    在局部變量表里,32位以內(nèi)的類型只占用一個(gè)slot(包括returnAddress類型),64位的類型(long和double)占用兩個(gè)slot。

    JVM會(huì)為局部變量表中的每一個(gè)Slot都分配一個(gè)訪問索引,通過這個(gè)索引即可成功訪問到局部變量表中指定的局部變量值。

    靜態(tài)變量與局部變量的對(duì)比

    變量的分類:😜

    • 按數(shù)據(jù)類型分:基本數(shù)據(jù)類型、引用數(shù)據(jù)類型
    • 按類中聲明的位置分:成員變量(類變量,實(shí)例變量)、局部變量
      • 類變量:linking的paper階段,給類變量默認(rèn)賦值,init階段給類變量顯示賦值即靜態(tài)代碼塊
      • 實(shí)例變量:隨著對(duì)象創(chuàng)建,會(huì)在堆空間中分配實(shí)例變量空間,并進(jìn)行默認(rèn)賦值
      • 局部變量:在使用前必須進(jìn)行顯式賦值,不然編譯不通過。

    🎅

    我們知道類變量表有兩次初始化的機(jī)會(huì),第一次是在“準(zhǔn)備階段”,執(zhí)行系統(tǒng)初始化,對(duì)類變量設(shè)置零值,另一次則是在“初始化”階段,賦予程序員在代碼中定義的初始值。

    類變量初始化不同的是,局部變量表不存在系統(tǒng)初始化的過程。這意味著如果創(chuàng)建了局部變量,并且在使用前不對(duì)它進(jìn)行顯示賦值,那么將無法通過編譯。

    在棧幀中,與性能調(diào)優(yōu)關(guān)系最為密切的部分就是前面提到的局部變量表。在方法執(zhí)行時(shí),虛擬機(jī)使用局部變量表完成方法的傳遞。
    🛀
    局部變量表中的變量也是重要的垃圾回收根節(jié)點(diǎn),只要被局部變量表中直接或間接引用的對(duì)象都不會(huì)被回收。

    2.6、操作數(shù)棧

    1、每一個(gè)獨(dú)立的棧幀除了包含局部變量表以外,還包含一個(gè)后進(jìn)先出(Last - In - First -Out)的 操作數(shù)棧,也可以稱之為 表達(dá)式棧(Expression Stack)🤪

    操作數(shù)棧,在方法執(zhí)行過程中,根據(jù)字節(jié)碼指令,往棧中寫入數(shù)據(jù)或提取數(shù)據(jù),即入棧(push)和 出棧(pop)

    • 某些字節(jié)碼指令將值壓入操作數(shù)棧,其余的字節(jié)碼指令將操作數(shù)取出棧。使用它們后再把結(jié)果壓入棧
    • 比如:執(zhí)行復(fù)制、交換、求和等操作
      🤦?♂?
      2、操作數(shù)棧,主要用于保存計(jì)算過程的中間結(jié)果,同時(shí)作為計(jì)算過程中變量臨時(shí)的存儲(chǔ)空間。

    3、每一個(gè)操作數(shù)棧都會(huì)擁有一個(gè)明確的棧深度用于存儲(chǔ)數(shù)值,其所需的最大深度在編譯期就定義好了,保存在方法的Code屬性中,為maxstack的值。

    4、操作數(shù)棧的每一個(gè)元素可以是任意Java數(shù)據(jù)類型,32位的數(shù)據(jù)類型占一個(gè)棧容量,64位的數(shù)據(jù)類型占2個(gè)棧容量,且在方法執(zhí)行的任意時(shí)刻,操作數(shù)棧的深度都不會(huì)超過max_stacks中設(shè)置的最大值。

    5、操作數(shù)棧并非采用訪問索引的方式來進(jìn)行數(shù)據(jù)訪問的,而是只能通過標(biāo)準(zhǔn)的入棧和出棧操作來完成一次數(shù)據(jù)訪問

    6、如果被調(diào)用的方法帶有返回值的話,其返回值將會(huì)被壓入當(dāng)前棧幀的操作數(shù)棧中,并更新PC寄存器中下一條需要執(zhí)行的字節(jié)碼指令。

    public void stackTest() {int a = 10;int b = 21;int c = a + b; } 0 bipush 10 // 10被壓入操作數(shù)堆棧。 2 istore_1 //從操作數(shù)堆棧中彈出一個(gè)數(shù) ,將這個(gè)數(shù)賦值給局部變量 a 這里istore_的索引之所以是一,而不是0,是因?yàn)榫植孔兞勘碇?#xff0c;第一個(gè)放進(jìn)去的是this。 static方法中 沒有 this,那個(gè)時(shí)候索引才是從0開始。3 bipush 215 istore_2 // 同上6 iload_1 // iload_<n> <n > 必須是當(dāng)前幀的局部變量數(shù)組的索引。< n >處的局部變量必須包含一個(gè)int. < n >處的局部變量的值被壓入操作數(shù)堆棧。7 iload_28 iadd // 執(zhí)行 add 操作9 istore_3 // 將結(jié)果賦值到局部變量 索引為3的變量上。 10 return

    我想看完這個(gè)gif動(dòng)圖 ,我想大家大概能夠明白操作數(shù)棧是一個(gè)什么樣的流程了吧,或者已經(jīng)明白了吧。如果沒有明白的話,可以留言評(píng)論哈。

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

    概述

    動(dòng)態(tài)鏈接(Dynamic Linking): 每個(gè)棧幀都保存了 一個(gè) 可以指向當(dāng)前方法所在類的 運(yùn)行時(shí)常量池, 目的是: 當(dāng)前方法中如果需要調(diào)用其他方法的時(shí)候, 能夠從運(yùn)行時(shí)常量池中找到對(duì)應(yīng)的符號(hào)引用, 然后將符號(hào)引用轉(zhuǎn)換為直接引用,然后就能直接調(diào)用對(duì)應(yīng)方法, 這就是動(dòng)態(tài)鏈接。 比如:invokedynamic指令
    👍

    在Java源文件被編譯到字節(jié)碼文件中時(shí),所有的變量和方法引用都作為符號(hào)引用(symbolic Reference)保存在class文件的常量池里。

    小思考:為什么需要運(yùn)行時(shí)常量池?

    因?yàn)樵诓煌姆椒?#xff0c;都可能調(diào)用常量或者方法,所以只需要存儲(chǔ)一份即可,節(jié)省了空間。

    常量池的作用:就是為了提供一些符號(hào)和常量,便于指令的識(shí)別

    比如:描述一個(gè)方法調(diào)用了另外的其他方法時(shí),就是通過常量池中指向方法的符號(hào)引用來表示的,那么動(dòng)態(tài)鏈接的作用就是為了將這些符號(hào)引用轉(zhuǎn)換為調(diào)用方法的直接引用。

    講這么這么多,沒有親眼見過,其實(shí)還是會(huì)對(duì)所謂的動(dòng)態(tài)鏈接感到陌生的,因?yàn)槲乙彩堑?#xff0c;所以接下來👇 給大家舉了栗子和圖哦。

    1、代碼部分

    2、通過 javap -v 類名.class 進(jìn)行反編譯后

    • main 方法

    • 描述一個(gè)方法調(diào)用了另外的其他方法時(shí),就是通過常量池中指向方法的符號(hào)引用來表示的 。

    • 注意圖中 調(diào)用test 方法中的那一行指令 invokevirtual #6 // Method test:()V

    • invokevirtual #6 :調(diào)用實(shí)例方法;基于類調(diào)度 。

    • 那么#6是什么意思呢? 這就牽扯到了常量池啦。

    • 我們接著來看一下 常量池(Constant pool)

    • #6 又接著指向了 #4.#33 但其實(shí) # 6 后面的注釋已經(jīng)講出來了。// StackFrameTest.test:()V

    • #4 是 Class, StackFrameTest 實(shí)例。

    • #33 又接著執(zhí)行#15:#9 也就是后面的注解 // test:() V

    • test 說的是方法名 ()V 說的返回值是 void。

    鏈接

    靜態(tài)鏈接:

    當(dāng)一個(gè)字節(jié)碼文件被裝載進(jìn)JVM內(nèi)部時(shí),如果被調(diào)用的目標(biāo)方法在編譯期克制,且運(yùn)行期保持不變時(shí),這種情況下降調(diào)用方法的符號(hào)引用轉(zhuǎn)換為直接引用的過程稱之為靜態(tài)鏈接

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

    如果被調(diào)用的方法在編譯期無法被確定下來,也就是說,只能夠在程序運(yùn)行期將調(diào)用的方法的符號(hào)轉(zhuǎn)換為直接引用,由于這種引用轉(zhuǎn)換過程具備動(dòng)態(tài)性,因此也被稱之為動(dòng)態(tài)鏈接。

    這個(gè)動(dòng)態(tài)鏈接只從粗略的角度講了,里面其實(shí)還有一些內(nèi)容沒講,考慮到篇幅過長(zhǎng),有時(shí)間會(huì)再補(bǔ)一篇?jiǎng)討B(tài)鏈接的文章。

    2.8、方法返回地址

    存放調(diào)用該方法的pc寄存器的值。當(dāng)一個(gè)方法開始執(zhí)行后,只有兩種方式可以退出這個(gè)方法:

    • 正常完成出口:執(zhí)行引擎遇到任意一個(gè)方法返回的字節(jié)碼指令(return),會(huì)有返回值傳遞給上層的方法調(diào)用者,簡(jiǎn)稱正常完成出口;究竟需要使用哪一個(gè)返回指令,還需要根據(jù)方法返回值的實(shí)際數(shù)據(jù)類型而定。

    • 異常完成出口 :在方法執(zhí)行過程中遇到異常(Exception),并且這個(gè)異常沒有在方法內(nèi)進(jìn)行處理,也就是只要在本方法的異常表中沒有搜索到匹配的異常處理器,就會(huì)導(dǎo)致方法退出,簡(jiǎn)稱異常完成出口。

    無論通過哪種方式退出,在方法退出后都返回到該方法被調(diào)用的位置。方法正常退出時(shí),調(diào)用者的pc計(jì)數(shù)器的值作為返回地址,即調(diào)用該方法的指令的下一條指令的地址。而通過異常退出的,返回地址是要通過異常表來確定,棧幀中一般不會(huì)保存這部分信息。

    異常表:

    方法執(zhí)行過程中,拋出異常時(shí)的異常處理,存儲(chǔ)在一個(gè)異常處理表,方便在發(fā)生異常的時(shí)候找到處理異常的代碼

    本質(zhì)上,方法的退出就是當(dāng)前棧幀出棧的過程。此時(shí),需要恢復(fù)上層方法的局部變量表、操作數(shù)棧、將返回值壓入調(diào)用者棧幀的操作數(shù)棧、設(shè)置PC寄存器值等,讓調(diào)用者方法繼續(xù)執(zhí)行下去。

    正常完成出口和異常完成出口的區(qū)別在于:通過異常完成出口退出的不會(huì)給他的上層調(diào)用者產(chǎn)生任何的返回值。

    2.9、一些附加信息

    棧幀中還允許攜帶與Java虛擬機(jī)實(shí)現(xiàn)相關(guān)的一些附加信息。例如:對(duì)程序調(diào)試提供支持的信息。


    課間休息會(huì)哈 接著看題啦

    面試

    面試提問:

    1、這個(gè)棧內(nèi)存大小是設(shè)置的越大越好嗎????是的話,是為什么?不是的話,又是為什么?

    • 不是,一定時(shí)間內(nèi)降低了OOM概率,但是會(huì)擠占其它的線程空間,因?yàn)檎麄€(gè)空間是有限的。

    2、垃圾回收是否涉及到虛擬機(jī)棧?

    • 不會(huì)

    3、方法中定義的局部變量是否線程安全?

    • 具體問題具體分析。看到這一點(diǎn)你可能會(huì)產(chǎn)生一些疑惑,我也理解。
    • 為什么會(huì)產(chǎn)生疑惑呢?我講過局部變量表是線程私有的,竟然都是私有的,肯定是線程安全的啊,但是這有一個(gè)前提的,如果這個(gè)局部變量在方法內(nèi)部產(chǎn)生,又在方法內(nèi)部消亡,生命周期是和棧楨相同的,那么可以肯定是它是線程安全的。但是如果這個(gè)方法是需要接收參數(shù),或者是需要返回值,那么這個(gè)時(shí)候就可以需要具體分析啦。

    自言自語(yǔ)

    這兩天河南發(fā)生了大暴雨,希望他們平安度過!!!


    兄弟們,還是一起躺平吧。內(nèi)卷太累辣吧。。。
    👩?💻->👨?💻🛌🛌

    總結(jié)

    以上是生活随笔為你收集整理的JVM-虚拟机栈详解 附面试高频题 (手画多图)!!!深入浅出,绝对值得收藏哈!!!的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。