最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

Java 虛擬機內(nèi)存區(qū)域劃分

2021-04-15 09:39 作者:光耀三十洲  | 我要投稿

在談 JVM 內(nèi)存區(qū)域劃分之前,我們先來看一下 Java 程序的具體執(zhí)行過程,我畫了一幅圖。

Java 源代碼文件經(jīng)過編譯器編譯后生成字節(jié)碼文件,然后交給 JVM 的類加載器,加載完畢后,交給執(zhí)行引擎執(zhí)行。在整個執(zhí)行的過程中,JVM 會用一塊空間來存儲程序執(zhí)行期間需要用到的數(shù)據(jù),這塊空間一般被稱為運行時數(shù)據(jù)區(qū),也就是常說的 JVM 內(nèi)存。

學(xué)習(xí)更多,請點擊:https://www.bilibili.com/video/BV1Zv411j7ms

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV13f4y1s7HS

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV13A411L7Mr

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV1s64y1m74i

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV1Qv411j7fG

所以,當(dāng)我們在談 JVM 內(nèi)存區(qū)域劃分的時候,其實談的就是這塊空間——運行時數(shù)據(jù)區(qū)。

大家應(yīng)該對官方出品的《Java 虛擬機規(guī)范》有所了解吧?了解這個規(guī)范可以讓我們更深入地理解 JVM。該規(guī)范主要包含 6 個部分,分別是:

  • 第一章:引言

  • 第二章:Java 虛擬機結(jié)構(gòu)

  • 第三章:Java 虛擬機編譯

  • 第四章:Class 文件

  • 第五章:加載、鏈接和初始化

  • 第六章:Java 虛擬機指令集

  • 第七章:操作碼

根據(jù)第二章 Java 虛擬機結(jié)構(gòu)中的規(guī)定,運行時數(shù)據(jù)區(qū)可以分為以下幾個部分,見下圖。

01、程序計數(shù)器

程序計數(shù)器(Program Counter Register)所占的內(nèi)存空間不大,很小一塊,可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼指令的行號指示器。字節(jié)碼解釋器會在工作的時候改變這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令,像分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等功能都需要依賴這個計數(shù)器來完成。

學(xué)習(xí)更多,請點擊:https://www.bilibili.com/video/BV1Zv411j7ms

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV13f4y1s7HS

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV13A411L7Mr

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV1s64y1m74i

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV1Qv411j7fG

在 JVM 中,多線程是通過線程輪流切換來獲得 CPU 執(zhí)行時間的,因此,在任一具體時刻,一個 CPU 的內(nèi)核只會執(zhí)行一條線程中的指令,因此,為了線程切換后能恢復(fù)到正確的執(zhí)行位置,每個線程都需要有一個獨立的程序計數(shù)器,并且不能互相干擾,否則就會影響到程序的正常執(zhí)行次序。

也就是說,我們要求程序計數(shù)器是線程私有的。

《Java 虛擬機規(guī)范》中規(guī)定,如果線程執(zhí)行的是非本地(native)方法,則程序計數(shù)器中保存的是當(dāng)前需要執(zhí)行的指令地址;如果線程執(zhí)行的是本地方法,則程序計數(shù)器中的值是 undefined。

學(xué)習(xí)更多,請點擊:https://www.bilibili.com/video/BV1Zv411j7ms

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV13f4y1s7HS

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV13A411L7Mr

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV1s64y1m74i

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV1Qv411j7fG

為什么本地方法在程序計數(shù)器中的值是 undefined 的?因為本地方法大多是通過 C/C++ 實現(xiàn)的,并未編譯成需要執(zhí)行的字節(jié)碼指令。

由于程序計數(shù)器中存儲的數(shù)據(jù)所占的空間不會隨程序的執(zhí)行而發(fā)生大小上的改變,因此,程序計數(shù)器是不會發(fā)生內(nèi)存溢出現(xiàn)象(OutOfMemory)的。

02、Java 虛擬機棧

Java 虛擬機棧中是一個個棧幀,每個棧幀對應(yīng)一個被調(diào)用的方法。當(dāng)線程執(zhí)行一個方法時,會創(chuàng)建一個對應(yīng)的棧幀,并將棧幀壓入棧中。當(dāng)方法執(zhí)行完畢后,將棧幀從棧中移除。棧遵循的是后進(jìn)先出的原則,所以線程當(dāng)前執(zhí)行的方法對應(yīng)的棧幀必定在 Java 虛擬機棧的頂部。

棧幀包含以下 5 個部分,見下圖。

學(xué)習(xí)更多,請點擊:https://www.bilibili.com/video/BV1Zv411j7ms

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV13f4y1s7HS

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV13A411L7Mr

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV1s64y1m74i

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV1Qv411j7fG

1)局部變量表

顧名思義,就是用來存儲方法中的局部變量的,包括方法的參數(shù)。對于基本數(shù)據(jù)類型的變量,直接存儲變量的值;對于引用類型的變量,存儲的是對象的引用。局部變量表的大小在編譯期間就確定了,程序執(zhí)行期間,它的大小是不會改變的。

2)操作數(shù)棧

表達(dá)式的計算是在操作數(shù)棧中完成的。當(dāng)一個方法剛開始執(zhí)行的時候,這個方法的操作數(shù)棧是空的,在方法的執(zhí)行過程中,會有各種字節(jié)碼指令往操作數(shù)棧中寫入和提取內(nèi)容,也就是入棧/出棧操作。例如,在做算術(shù)運算的時候是通過操作數(shù)棧來進(jìn)行的,又或者在調(diào)用其他方法的時候是通過操作數(shù)棧來進(jìn)行參數(shù)傳遞的。

學(xué)習(xí)更多,請點擊:https://www.bilibili.com/video/BV1Zv411j7ms

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV13f4y1s7HS

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV13A411L7Mr

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV1s64y1m74i

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV1Qv411j7fG

3)指向運行時常量池的引用

當(dāng)前方法所屬的類的運行時常量池的引用,引用其他的常量類或者使用字符串常量池中的字符串。

4)方法返回地址

方法執(zhí)行完(不論是正常執(zhí)行還是發(fā)生了異常)后需要返回到方法被調(diào)用的位置,程序才能繼續(xù)執(zhí)行,方法返回地址保存一些用來幫助恢復(fù)上層方法的執(zhí)行狀態(tài)的信息。

5)動態(tài)鏈接

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

學(xué)習(xí)更多,請點擊:https://www.bilibili.com/video/BV1Zv411j7ms

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV13f4y1s7HS

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV13A411L7Mr

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV1s64y1m74i

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV1Qv411j7fG

與程序計數(shù)器一樣,Java 虛擬機棧也是線程私有的,它的生命周期和線程相同,描述的是 Java 方法執(zhí)行的內(nèi)存模型,每次方法調(diào)用的數(shù)據(jù)都是通過棧傳遞的。

Java 虛擬機棧會出現(xiàn)兩種錯誤:

  • StackOverFlowError:當(dāng)線程請求棧的深度超過 Java 虛擬機棧的最大深度的時候拋出。

  • OutOfMemoryError:如果 Java 虛擬機棧允許動態(tài)擴容,當(dāng)棧擴容時無法申請到足夠的內(nèi)存時拋出。

最有名的 HotSpot 虛擬機的棧容量是不允許動態(tài)擴容的,所以在 HotSpot 虛擬機上是不會出現(xiàn) OutOfMemoryError 的。

學(xué)習(xí)更多,請點擊:https://www.bilibili.com/video/BV1Zv411j7ms

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV13f4y1s7HS

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV13A411L7Mr

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV1s64y1m74i

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV1Qv411j7fG

03、本地方法棧

本地方法棧與 Java 虛擬機棧類似,區(qū)別是本地方法棧執(zhí)行的是本地方法,也就是帶有 native 關(guān)鍵字修飾的方法。

在 HotSpot 虛擬機中,本地方法棧和 Java 虛擬機棧不做區(qū)分。

04、堆

堆是所有線程共享的一塊內(nèi)存區(qū)域,在 Java 虛擬機啟動的時候創(chuàng)建,用來存儲對象(數(shù)組也是一種對象)。

以前,Java 中“幾乎”所有的對象都會在堆中分配,但隨著 JIT(Just-In-Time)編譯器的發(fā)展和逃逸技術(shù)的逐漸成熟,所有的對象都分配到堆上漸漸變得不那么“絕對”了。從 JDK 7 開始,Java 虛擬機已經(jīng)默認(rèn)開啟逃逸分析了,意味著如果某些方法中的對象引用沒有被返回或者未被外面使用(也就是未逃逸出去),那么對象可以直接在棧上分配內(nèi)存。

簡單解釋一下 JIT 和逃逸分析。

學(xué)習(xí)更多,請點擊:https://www.bilibili.com/video/BV1Zv411j7ms

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV13f4y1s7HS

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV13A411L7Mr

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV1s64y1m74i

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV1Qv411j7fG

常見的編譯型語言如 C++,通常會把代碼直接編譯成 CPU 所能理解的機器碼來運行。而 Java 為了實現(xiàn)“一次編譯,處處運行”的特性,把編譯的過程分成兩部分,首先它會先由 javac 編譯成通用的中間形式——字節(jié)碼,然后再由解釋器逐條將字節(jié)碼解釋為機器碼來執(zhí)行。所以在性能上,Java 可能會干不過 C++ 這類編譯型語言。

為了優(yōu)化 Java 的性能 ,JVM 在解釋器之外引入了 JIT 編譯器:當(dāng)程序運行時,解釋器首先發(fā)揮作用,代碼可以直接執(zhí)行。隨著時間推移,即時編譯器逐漸發(fā)揮作用,把越來越多的代碼編譯優(yōu)化成本地代碼,來獲取更高的執(zhí)行效率。解釋器這時可以作為編譯運行的降級手段,在一些不可靠的編譯優(yōu)化出現(xiàn)問題時,再切換回解釋執(zhí)行,保證程序可以正常運行。

逃逸分析(Escape Analysis),簡單來講就是,Hotspot 虛擬機可以分析新創(chuàng)建對象的使用范圍,并決定是否在 Java 堆上分配內(nèi)存的一項技術(shù)。

堆是 Java 垃圾收集器管理的主要區(qū)域,因此也被稱作 GC 堆(Garbage Collected Heap)。從垃圾回收的角度來看,由于垃圾收集器基本都采用了分代垃圾收集的算法,所以堆還可以細(xì)分為:新生代和老年代。新生代還可以細(xì)分為:Eden 空間、From Survivor、To Survivor 空間等。進(jìn)一步劃分的目的是更好地回收內(nèi)存,或者更快地分配內(nèi)存。

堆這最容易出現(xiàn)的就是 OutOfMemoryError 錯誤,分為以下幾種表現(xiàn)形式:

  • OutOfMemoryError: GC Overhead Limit Exceeded:當(dāng) JVM 花太多時間執(zhí)行垃圾回收并且只能回收很少的堆空間時,就會發(fā)生該錯誤。

  • java.lang.OutOfMemoryError: Java heap space:假如在創(chuàng)建新的對象時, 堆內(nèi)存中的空間不足以存放新創(chuàng)建的對象, 就會引發(fā)該錯誤。和本機的物理內(nèi)存無關(guān),和我們配置的虛擬機內(nèi)存大小有關(guān)!

05、元空間

JDK 8 的時候,原有的方法區(qū)(更準(zhǔn)確的說應(yīng)該是永久代)被徹底移除,取而代之的是元空間。

我們來說說方法區(qū)吧。方法區(qū)和堆一樣,是線程共享的區(qū)域,它用來存儲已經(jīng)被 Java 虛擬機加載的類信息、常量、靜態(tài)變量,以及便器編譯后的代碼等。

學(xué)習(xí)更多,請點擊:https://www.bilibili.com/video/BV1Zv411j7ms

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV13f4y1s7HS

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV13A411L7Mr

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV1s64y1m74i

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV1Qv411j7fG

在有些地方,方法區(qū)也被稱為永久代。但其實不能這么理解。

《Java 虛擬機規(guī)范》中只規(guī)定了有方法區(qū)這么一個概念和它的作用,并沒有規(guī)定如何去實現(xiàn)它。那么不同的 Java 虛擬機可能就會有不同的實現(xiàn)。永久代是 HotSpot 對方法區(qū)的一種實現(xiàn)形式。也就是說,永久代只是 HotSpot 中的一個概念,而方法區(qū)則是 Java 虛擬機規(guī)范中的一個定義,一種規(guī)范。

換句話說,方法區(qū)和永久代的關(guān)系就像是 Java 中接口和類的關(guān)系,類實現(xiàn)了接口。

在方法區(qū)中,還有一塊非常重要的部分,也就是運行時常量池。在講 class 文件的時候,提到了每個 class 文件都會有個常量池,用來存放字符串常量、類和接口的名字、字段名、常量等等。運行時常量池和 class 文件的常量池是一一對應(yīng)的,它就是通過 class 文件中的常量池來構(gòu)建的。

JDK 7 之前,運行時常量池中包含著字符串常量池,都在方法區(qū)。

JDK 7 的時候,字符串常量池從方法區(qū)中拿出來放到了堆中,運行時常量池中的其他東西還在方法區(qū)中。

學(xué)習(xí)更多,請點擊:https://www.bilibili.com/video/BV1Zv411j7ms

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV13f4y1s7HS

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV13A411L7Mr

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV1s64y1m74i

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV1Qv411j7fG

JDK 8 的時候,HotSpot 移除了永久代,也就是說方法區(qū)不存在了,取而代之的是元空間。也就意味著字符串常量池在堆中,運行時常量池跑到了元空間。

再來說說為什么要將永久代 (PermGen) 或者說方法區(qū)替換為元空間 (MetaSpace) 。

第一,永久代放在 Java 虛擬機中,就會受到 Java 虛擬機內(nèi)存大小的限制,而元空間使用的是本地內(nèi)存,也就脫離了 Java 虛擬機內(nèi)存的限制。

第二,JDK 8 的時候,在 HotSpot 中融合了 JRockit 虛擬機,而 JRockit 中并沒有永久代的概念,因此新的 HotSpot 就沒有必要再開辟一塊空間來作為永久代了。

對于我們 Java 程序員來說,不需要像 C/C++ 程序員那樣時時刻刻關(guān)心著內(nèi)存泄露和內(nèi)存溢出的問題,但實際的工作中,這兩個問題出現(xiàn)的頻率還是蠻高的,尤其是在多線程并發(fā)的情況下。如果不了解 Java 虛擬機是如何管理內(nèi)存的,那么一旦遇到問題可能就會束手無策。

了解 Java 虛擬機的內(nèi)存區(qū)域劃分有助于我們更好的去理解 Java 虛擬機,從而掌握內(nèi)存問題排查的主動權(quán)。

學(xué)習(xí)更多,請點擊:https://www.bilibili.com/video/BV1Zv411j7ms

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV13f4y1s7HS

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV13A411L7Mr

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV1s64y1m74i

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://www.bilibili.com/video/BV1Qv411j7fG


作者:沉默王二
鏈接:https://juejin.cn/post/6950927106565996551
來源:掘金
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。


Java 虛擬機內(nèi)存區(qū)域劃分的評論 (共 條)

分享到微博請遵守國家法律
青龙| 平陆县| 吴川市| 鹤岗市| 大洼县| 内黄县| 高安市| 新郑市| 开鲁县| 聂荣县| 华宁县| 缙云县| 正阳县| 大名县| 通山县| 民权县| 崇礼县| 邓州市| 交口县| 石首市| 呼和浩特市| 安徽省| 泽普县| 观塘区| 额济纳旗| 芦山县| 南木林县| 鹤峰县| 宁远县| 荣昌县| 宁强县| 石林| 合川市| 伊吾县| 鹤峰县| 温宿县| 湖北省| 来宾市| 齐河县| 西丰县| 乐都县|