Java虛擬機(jī)(JVM)系列三
運(yùn)行時數(shù)據(jù)區(qū)
一.運(yùn)行時數(shù)據(jù)區(qū)整體架構(gòu)
- JVM定義了若干種程序在運(yùn)行期間會使用到運(yùn)行時數(shù)據(jù)區(qū)啼染,其中有一些會隨著JVM啟動而創(chuàng)建,退出而銷毀(進(jìn)程)锚沸,另一些則與線程一一對應(yīng)笔时,這些與線程對應(yīng)的數(shù)據(jù)區(qū)會隨著線程的開始和結(jié)束而創(chuàng)建和銷毀(線程)
- 方法區(qū)和堆是進(jìn)程級別的颓屑,屬于被共享的
- 棧粒褒、本地方法棧、程序計(jì)數(shù)器是每個線程獨(dú)有一份
- 每個JVM只有一個運(yùn)行時實(shí)例诚镰,即Runtime實(shí)例
- 在HotSpot虛擬機(jī)中奕坟,每個線程與操作系統(tǒng)的本地線程直接映射
二.程序計(jì)數(shù)器(也叫PC寄存器 不存在GC祥款、OOM)
1.作用
- PC寄存器是用來存儲指向下一條指令的地址,由執(zhí)行引擎讀取指令并執(zhí)行
2.簡介
- PC寄存器是一塊很小的空間月杉,幾乎可以忽略不計(jì)刃跛,也是運(yùn)行速度最快的存儲區(qū)域
- 在JVM規(guī)范中,每個線程都有自己的的一個程序計(jì)數(shù)器
- 任何時間一個線程都只有一個方法在執(zhí)行苛萎,即當(dāng)前方法
- 字節(jié)碼解釋器工作時就是通過改變這個計(jì)數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令
-
它是唯一一個在JVM規(guī)范中沒有規(guī)定任何OutOfMemoryError的情況的區(qū)域
3.面試時的問題
- 使用PC寄存器存儲字節(jié)碼指令地址有什么用呢(為什么使用PC寄存器記錄當(dāng)前線程的執(zhí)行地址呢)桨昙?
- 1)因?yàn)镃PU需要不停的切換各個線程,切換回來后腌歉,需要知道該執(zhí)行哪個指令了
- 2)JVM解釋器需要通過改變PC寄存器的值來獲取下一條應(yīng)該執(zhí)行什么樣的字節(jié)碼指令
- PC寄存器為什么被設(shè)定為私有的
為了可以準(zhǔn)確記錄各個線程正在執(zhí)行的當(dāng)前字節(jié)碼指令地址蛙酪,確保各線程間獨(dú)立計(jì)算,不互相干擾
4.額外
- 并行:一個時刻同時執(zhí)行翘盖,并行的關(guān)鍵是同時做很多事情
- 并發(fā):一個時間段內(nèi)交替執(zhí)行桂塞,并發(fā)的關(guān)鍵是同時管理很多事情
三.棧(虛擬機(jī)棧 存在OOM,不存在GC)
1.簡介
- Java虛擬機(jī)棧在每個線程創(chuàng)建的時候被創(chuàng)建馍驯,其內(nèi)部是一個個的棧幀阁危,對應(yīng)一次次的Java方法調(diào)用
- 主管Java的運(yùn)行,它保存局部變量(8種基本數(shù)據(jù)類型汰瘫、對象的陰影)狂打、部分結(jié)果,并參與方法的調(diào)用和返回
2.棧的特點(diǎn)
- 棧是一種快速有效的分配存儲結(jié)構(gòu)混弥,速度僅次于PC寄存器
- 只有進(jìn)棧和出棧的操作趴乡,具有先進(jìn)后出的特點(diǎn)
3.棧與堆
棧解決的是數(shù)據(jù)運(yùn)行的問題(也存儲局部變量,中間結(jié)果)剑逃,堆解決的是數(shù)據(jù)存儲問題(主要存儲的是對象)
4.面試中遇到的問題
- 開發(fā)中遇到的異常
棧中可能出現(xiàn)的異常(StackOverflowError:自己調(diào)用自己 OutOfMemoryError)
5.設(shè)置棧參數(shù)
- 設(shè)置棧大姓阋恕( -Xss256k ----設(shè)置棧大小為256k)
6.棧的存儲單位
- 棧的存儲單位是棧幀,每個方法對應(yīng)一個棧幀(Stack Frame)
- 三個名詞:當(dāng)前棧幀 當(dāng)前方法 當(dāng)前類
- 執(zhí)行引擎運(yùn)行的所有字節(jié)碼指令只對當(dāng)前棧幀進(jìn)行操作
7.棧楨
每個棧幀中存儲著
局部變量表(LC Local Variable)
操作數(shù)棧(Operant Stack 或表達(dá)式棧)
動態(tài)鏈接(Dynamic Linking 或指向運(yùn)行時常量池的方法引用)
返回地址(Return Address 或方法正常退出或者異常退出的定義)
-
一些附加信息
7.1局部變量表(Local Variable)
- 局部變量表也被稱為是局部變量數(shù)組或本地變量表
- 定義為一個數(shù)字?jǐn)?shù)組蛹磺,主要存儲的是方法參數(shù)以及在方法內(nèi)聲明的局部變量(八大基本數(shù)據(jù)類型粟瞬,對象的 引用以及返回地址類型)
- 局部變量表是創(chuàng)建在線程的棧上面的,是線程的私有數(shù)據(jù)萤捆,因此不存在數(shù)據(jù)安全問題
- 局部變量表的大腥蛊贰(即數(shù)組長度)是在編譯的時候就定下來的,并且不可以改變
- 局部變量表中的變量只在當(dāng)前方法內(nèi)有效
- 局部變量表的基本存儲單元是Slot(變量槽)俗或,32位以內(nèi)的數(shù)據(jù)類型占用一個Slot(byte市怎、short、int辛慰、float区匠、char在存儲前被轉(zhuǎn)換為int、boolean也被轉(zhuǎn)換為int、以及返回地址類型)驰弄,64位的類型(long和double)占據(jù)兩個Slot
- JVM會為局部變量表中的每一個Slot分配一個訪問索引麻汰,以便訪問到局部變量表中指定的局部變量值
- 當(dāng)一個實(shí)例方法被調(diào)用的時候,它的方法參數(shù)以及內(nèi)部的局部變量都會被按照順序復(fù)制到局部變量表中的Slot上
- 如果當(dāng)前棧幀是由構(gòu)造方法或者實(shí)例方法(即非靜態(tài)方法)創(chuàng)建的戚篙,那么該對象的引用this將會被存儲到index為0的slot處(所以當(dāng)我們在靜態(tài)方法中使用this時會報錯)
- 棧幀中局部變量表中的Slot(槽位)是可以重復(fù)利用五鲫,如果一個局部變量過了作用域,那么在后面聲明的局部變量很可能會復(fù)用過期的局部變量的槽位岔擂,從而達(dá)到節(jié)省資源的目的
- 局部變量中的變量也是重要的垃圾回收根節(jié)點(diǎn)位喂,只要被局部變量中直接或間接引用的對象都不會被回收
7.2 操作數(shù)棧(Operand Stack)
棧可以使用數(shù)組或者鏈表實(shí)現(xiàn)乱灵,只允許進(jìn)棧和出棧操作
每一個獨(dú)立的棧幀中除了包括局部變量表塑崖,還包含一個先進(jìn)后出的操作數(shù)棧
操作數(shù)棧,在方法執(zhí)行過程中阔蛉,根據(jù)字節(jié)碼指令,往棧中寫入數(shù)據(jù)或取出數(shù)據(jù)聋呢,即入棧和出棧
操作數(shù)棧颠区,主要用于保存計(jì)算過程中的中間結(jié)果削锰,同時作為計(jì)算過程中變量的臨時存儲空間
每一個操作數(shù)棧,都有一個確定的棧深毕莱,在編譯時就確定(max_stack的值)
操作數(shù)棧數(shù)據(jù)的訪問是通過入棧和出棧實(shí)現(xiàn),而不是通過索引進(jìn)行訪問蛹稍,這與指定局部變量值的訪問不同
被調(diào)用的方法如果有返回值,其返回值會被壓入當(dāng)前棧幀的操作數(shù)棧中部服,并更新PC寄存器中下一條需要執(zhí)行的字節(jié)碼指令
我們所說的Java虛擬機(jī)的解釋引擎是基于棧的執(zhí)行引擎唆姐,這個棧就是值操作數(shù)棧
-
棧頂緩存技術(shù)(為了解決數(shù)據(jù)頻繁進(jìn)棧出棧廓八,即內(nèi)存對數(shù)據(jù)頻繁的讀/寫操作進(jìn)而影響速度,提出的此技術(shù)剧蹂,它的核心思想是將棧頂元素全部緩存在物理CPU的寄存器中)
7.3 動態(tài)鏈接(Dynamic Linking 也叫做 指向運(yùn)行時常量池的方法引用)
- 每一個棧幀內(nèi)部都包含一個指向運(yùn)行時常量池的該棧幀所屬方法的引用宠叼。其目的是為了當(dāng)前方法的代碼能實(shí)現(xiàn)動態(tài)鏈接(如 invokerdynamic指令)
- java源文件被編譯成字節(jié)碼文件的時候,所有的變量和方法都作為符號引用【類加載子系統(tǒng)鏈接階段的解析步驟】保存在class文件的常量池里。動態(tài)鏈接的作用就是將這些符號引用轉(zhuǎn)換為調(diào)用方法的直接引用
7.4 方法返回地址(Return Address)
- 存放調(diào)用該方法的pc寄存器的值(pc寄存器存儲的是該方法要執(zhí)行的下一條指令的值)醋闭,即調(diào)用該方法的指令的下一條指令的地址朝卒。
- 方法的正常退出和異常推出的區(qū)別是:通過異常完成出口退出的 給他的上層調(diào)用者產(chǎn)生任何的返回值乐埠。
- 一個方法在正常調(diào)用完成后酒精返回哪一一個返回指令需要根據(jù)具體的返回值類型確定丈咐。
- 在字節(jié)碼指令中,返回指令包括
ireturn:返回值類型是 byte棵逊、char、short徒像、int蛙讥、boolean
freturn:返回值類型是float
dreturn:返回值類型是double
areturn:返回值類型是引用類型
return:該方法為void方法、實(shí)例初始化方法【構(gòu)造器】次慢、類和接口的初始化方法【構(gòu)造器】)
7.5 一些附加信息
棧幀中還允許攜帶與JVM實(shí)現(xiàn)相關(guān)的一些附加信息旁涤。如對程序調(diào)試提供支持的信息
8.方法調(diào)用
- 在JVM中迫像,將符號引用轉(zhuǎn)換為調(diào)用方法的直接引用與方法的綁定機(jī)制有關(guān)
綁定:一個字段、方法或類的符號引用被替換為直接引用的過程菌羽,僅發(fā)生一次
靜態(tài)鏈接:當(dāng)一個字節(jié)碼文件被裝載到JVM內(nèi)部時纷闺,如果被調(diào)用的目標(biāo)方法,在編譯期可知犁功,且運(yùn)行期保持不變。這種情況下將調(diào)用方法的符號引用轉(zhuǎn)換為直接引用的過程叫靜態(tài)鏈接署鸡,對應(yīng)的綁定機(jī)制是早期綁定
動態(tài)鏈接:如果在被調(diào)用的方法在編譯期不確定,只能在程序運(yùn)行的期將調(diào)用方法的符號引用轉(zhuǎn)換為直接引用时捌。這種轉(zhuǎn)換過程具備動態(tài)性炉抒,因此稱之為動態(tài)鏈接,對應(yīng)的綁定機(jī)制是晚期綁定 - 非虛方法與虛方法
如果方法在編譯期就確定了調(diào)用的版本拿诸,在運(yùn)行時保持不變塞茅,則該方法叫非虛方法
靜態(tài)方法、final修飾的方法描沟、私有方法鞭光、實(shí)例構(gòu)造器、父類方法都是非虛方法迟蜜,其他都是虛方法 - 虛擬機(jī)中提供了以下方法的調(diào)用指令
invokestatic:調(diào)用靜態(tài)方法啡省,解析階段確定唯一版本
invokespecial:調(diào)用<init>方法卦睹、私有及父類方法、解析階段確定唯一版本
invokevirtual: 調(diào)用所有虛方法
invokeinterface: 調(diào)用接口方法
invokedynamic:動態(tài)解析出需要調(diào)用的方法障斋,然后執(zhí)行徐鹤。Java8中的Lamda表達(dá)式的出現(xiàn),使得此指令的生成遂庄,在Java中才有了直接的生成方法
invokestatic和invokespecial指令調(diào)用的方法叫非虛方法劲赠,其余的(final修飾的方法除外)稱為虛方法 - 靜態(tài)類型語言與動態(tài)類型語言
靜態(tài)類型語言與動態(tài)類型語言的區(qū)別在于對于類型的檢查是在編譯期還是在運(yùn)行期秸谢,滿足前者的是靜態(tài)類型語言估蹄。靜態(tài)類型語言是判斷變量本身的類型信息沫换,動態(tài)類型語言是判斷變量值的類型信息。變量沒有類型信息刊棕,變量值才有類型信息待逞,這是動態(tài)語言的一個重要特性(如JS) - 重寫(是動態(tài)分派的典型代表)
1)當(dāng)調(diào)用一個對象的方法的時候网严,我們會將對象壓入操作數(shù)棧,根據(jù)字節(jié)碼指令(invokevirtual)怜庸,尋找該對象的實(shí)際類型垢村,記做C
2)如果在類型C中找到與常量池中描述符合簡單名稱都符合的方法嘉栓,則進(jìn)行訪問權(quán)限的校驗(yàn),如果通過侵佃,則返回該方法的直接引用馋辈,若不通過,則返回java.lang.IllegalAcessEror異常
3)若在常量中未找到符合的方法叉抡,按照繼承關(guān)系從下往上依次對C 的父類進(jìn)行第二步的搜索和驗(yàn)證答毫。
4)如果始終未找到,則拋出java.lang.AbstrackMethodError異常 - 虛方法表
在面向?qū)ο缶幊讨兄嵘樱瑫l繁的使用動態(tài)分派,如果每次在動態(tài)分派過程中都進(jìn)行搜索和驗(yàn)證的步驟侦锯,必然會影響到執(zhí)行效率秦驯,為了提升性能,JVM在類的方法區(qū)建立一個虛方法表
每個類中都有一個虛方法表亲桥,表中存放各個方法的實(shí)際入口固耘,即真正調(diào)用的放方法
虛方法表在類加載的鏈接階段的解析步驟中被創(chuàng)建并初始化
如圖撤防,cat()類中的toString()是重寫Object類中方法村砂,所以toString()實(shí)際所屬類型是Cat類的
9.虛擬機(jī)棧的面試題
- 舉例棧溢出的情況(StackOverFlowError)葫笼?
通過 -Xss設(shè)置棧的大小 - 調(diào)整棧大小路星,就能保證不出現(xiàn)棧溢出么诱桂?
不能保證〉姘ぃ可以設(shè)置棧大小確保出現(xiàn)的幾率降低触菜,但不能保證不初選棧溢出 - 分配的棧內(nèi)存越大越好么?
不是的哲泊〈呋龋總內(nèi)存數(shù)是一定的,如果某個棧內(nèi)存設(shè)置太大先朦,會擠壓其他內(nèi)存結(jié)構(gòu)的空間 - 垃圾回收是否會涉及到虛擬機(jī)棧
不會。
名稱 | error | gc |
---|---|---|
程序計(jì)數(shù)器 | 不出現(xiàn) | 不出現(xiàn) |
虛擬機(jī)棧 | 出現(xiàn) | 不出現(xiàn) |
本地方法棧 | 出現(xiàn) | 不出現(xiàn) |
方法區(qū) | 出現(xiàn) | 出現(xiàn) |
堆 | 出現(xiàn) | 出現(xiàn) |
- 方法中定義的局部變量是否線程安全?
具體問題具體分析刺彩。(如果局部變量是內(nèi)部產(chǎn)生的创倔,并且在方法內(nèi)消亡的,則是線程安全的畦攘。如果是作為參數(shù)傳進(jìn)來的知押,或者作為方法返回值返回,則會存在線程安全問題)
何為線程安全?
--如果只有一個縣城去操作此數(shù)據(jù)偎漫,則此數(shù)據(jù)必是線程安全的象踊。
--如果多個線程操作此數(shù)據(jù),則此數(shù)據(jù)是共享數(shù)據(jù)栈虚。如果不考慮同步機(jī)制的話史隆,會存在線程安全問題
四.本地方法棧
Java虛擬機(jī)棧是用來管理Java程序的調(diào)用,而本地方法棧是用來管理本地方法的調(diào)用
- 本地方法棧也是線程私有的
- 當(dāng)我們調(diào)用本地方法的時候粘姜,會將此本地方法壓入本地方法棧中熔酷,在執(zhí)行引擎執(zhí)行時加載本地方法庫
五.本地方法接口(不屬于運(yùn)行時數(shù)據(jù)區(qū))
- 一個Native Mehod 就是 Java調(diào)用非Java代碼的接口
- 在定義一個Native Mehotd的時候拒秘,不需要提供 實(shí)現(xiàn)體臭猜,因?yàn)閷?shí)現(xiàn)體是由非Java語言在外面實(shí)現(xiàn)的
thread押蚤、Object等類中有多個本地方法 - 標(biāo)識符native可以與所有其他的java標(biāo)識符連用, abstract除外(abstract是沒有實(shí)現(xiàn)體丐膝,但是native是有實(shí)現(xiàn)體的钾菊,只不過是用非java語言實(shí)現(xiàn)的 )
- 為什么要使用本地方法煞烫?
- 1.與Java外環(huán)境交互
有時Java應(yīng)用要與Java外環(huán)境交互,這是本地方法存在的主要原因 - 2.與操作系統(tǒng)交互
Java代碼是運(yùn)行在JVM上的凛俱,但是JVM畢竟不是一個完整的系統(tǒng)料饥,經(jīng)常依賴一些底層系統(tǒng)的支持,使用本地方法原叮,我們得以使用Java實(shí)現(xiàn)了jre與底層系統(tǒng)的交互巡蘸。還有我們可以通過本地方法使用java語言本身沒有提供封裝的操作系統(tǒng)的特性悦荒。 - 3.Sun's Java
sun的解釋器本身使用C語言編寫的
六.堆
- 1.與Java外環(huán)境交互
- 堆的知識點(diǎn)多,單獨(dú)拿一小節(jié)細(xì)說