JVM系列(二)

Java內(nèi)存區(qū)域

1. 運(yùn)行時(shí)數(shù)據(jù)區(qū)域

在上一篇博客中提到了虛擬機(jī)的運(yùn)行的時(shí)候,需要加載類,以及存儲(chǔ)數(shù)據(jù)等,因此需要有個(gè)區(qū)域用來存儲(chǔ)運(yùn)行時(shí)的數(shù)據(jù)。

image

上一篇博客也提到了JVM的體系結(jié)構(gòu),可以看出這幅圖中的運(yùn)行時(shí)數(shù)據(jù)區(qū)的劃分是和JVM體系結(jié)構(gòu)相關(guān)的。

Java虛擬機(jī)在執(zhí)行Java程序的過程中會(huì)把自己所管理的內(nèi)存區(qū)域劃分成若干個(gè)數(shù)據(jù)區(qū)域嗎,這些區(qū)域都有各自的用途以及創(chuàng)建和銷毀的時(shí)間,有的區(qū)域隨著虛擬機(jī)進(jìn)程的啟動(dòng)而存在,有些區(qū)域則依賴用戶的線程的啟動(dòng)和結(jié)束而建立和銷毀。

1.PC寄存器

PC寄存器,也就是我們常說的程序計(jì)數(shù)器,是一塊較小的內(nèi)存空間,用于存放一條指令的地址, 這條指令便是虛擬機(jī)要執(zhí)行的下一條指令。它可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。在虛擬機(jī)的概念模型中,字節(jié)碼解釋器就是通過改變這個(gè)計(jì)數(shù)器的值來選取下一條要執(zhí)行的指令。

每條線程都需要有一個(gè)獨(dú)立的程序計(jì)數(shù)器,因?yàn)槎嗑€程是通過線程輪流切換并分配時(shí)間處理執(zhí)行的方式實(shí)現(xiàn)的,因此為了保證線程切換之后,能恢復(fù)到正確的執(zhí)行位置,每條線程都需要有一個(gè)獨(dú)立的程序計(jì)數(shù)器,且各個(gè)線程的程序計(jì)數(shù)器互不影響。這類內(nèi)存區(qū)域稱為線程私有的內(nèi)存。

如果線程正在執(zhí)行的是一個(gè)Java方法,則這個(gè)計(jì)數(shù)器記錄的是正在執(zhí)行的指令地址,如果是Native方法,這個(gè)計(jì)數(shù)器的值就為空,此區(qū)域是唯一一個(gè)Java虛擬機(jī)規(guī)范沒有規(guī)定任何OOM情況的區(qū)域。

2.Java棧

與PC寄存器一樣,Java棧也是線程私有的,它的生命周期與線程相同,每個(gè)方法的執(zhí)行和結(jié)束都對(duì)應(yīng)著一個(gè)棧幀(棧的基本單位)的入棧和出棧。每個(gè)方法對(duì)應(yīng)一個(gè)棧幀,用于存儲(chǔ)局部表,操作數(shù)棧,動(dòng)態(tài)鏈接,方法出口等。

局部變量表的內(nèi)存空間是在編譯期間完成分配的,當(dāng)進(jìn)入一個(gè)方法時(shí),整個(gè)方法需要在幀中分配多大的局部變量空間是完全確定的。

在Java虛擬機(jī)規(guī)范中,對(duì)這個(gè)區(qū)域規(guī)定了兩種異常情況,一個(gè)是StackOverflowError異常,如果一個(gè)線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度,將會(huì)拋出這個(gè)異常。一個(gè)是OOM異常,對(duì)于可以動(dòng)態(tài)擴(kuò)展的虛擬機(jī)棧,如果擴(kuò)展無法申請(qǐng)到足夠的內(nèi)存,便會(huì)報(bào)出此異常。

3.本地方法棧

本地方法棧是為Native方法服務(wù)的,而Java棧是為Java方法服務(wù)的,當(dāng)Java調(diào)用C/C++等Native方法時(shí), 便會(huì)使用本地方法棧,它也是線程私有的。

4.Java堆

Java堆是被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機(jī)被啟動(dòng)時(shí)創(chuàng)建,此內(nèi)存區(qū)域的唯一目的便是存放對(duì)象實(shí)例以及數(shù)組,在java中數(shù)組也是對(duì)象。虛擬機(jī)規(guī)范的描述是:所有的對(duì)象實(shí)例和數(shù)組都要在堆上分配。(實(shí)例變量是存儲(chǔ)在堆上的對(duì)象中的,一個(gè)對(duì)象有一份實(shí)例變量)

隨著逃逸分析和TLAB技術(shù)的成熟,便出現(xiàn)了對(duì)象實(shí)例不一定在堆上分配空間的情況。在此介紹下這兩個(gè)技術(shù)。

逃逸技術(shù)

逃逸分析,是一種可以有效減少Java 程序中同步負(fù)載和內(nèi)存堆分配壓力的跨函數(shù)全局?jǐn)?shù)據(jù)流分析[算法。通過逃逸分析,Hotspot編譯器能夠分析出一個(gè)新的對(duì)象的引用的使用范圍從而決定是否要將這個(gè)對(duì)象分配到堆上。

逃逸分析是指分析指針動(dòng)態(tài)范圍的方法,當(dāng)變量(或者對(duì)象)在方法中分配后,其指針有可能被返回或者被全局引用,這樣就會(huì)被其他過程或者線程所引用,這種現(xiàn)象稱作指針(或者引用)的逃逸(Escape)。

對(duì)象的三種逃逸狀態(tài)

GlobalEscape(全局逃逸):即一個(gè)對(duì)象的引用逃出了方法和線程,如:一個(gè)對(duì)象的引用是賦值給類變量,或者作為返回值返回給調(diào)用方法。

ArgEscape(參數(shù)級(jí)逃逸):即在方法調(diào)用過程中,將對(duì)象的引用作為參數(shù)傳遞給方法。

NoEscape(沒有逃逸):只是在方法中new出來,在方法中使用,可以不將這種對(duì)象分配在傳統(tǒng)的堆上,而是分配在棧上。

public void test(){
    A a = new A();
    B b = new B();
    A.XXX(b);
}

上面是一個(gè)簡單的實(shí)例,按照我們以往的想法,無非是對(duì)象的引用a,b分配在棧中,而new出來的A,B對(duì)象是存儲(chǔ)在堆中的,而使用了逃逸技術(shù),顯然new出的A對(duì)象是屬于沒有逃逸的,而new出的B對(duì)象雖然作為參數(shù)傳遞給了A的方法,然而A是沒有逃逸的,因此這兩個(gè)對(duì)象都是屬于NoEscape,可分配在棧中。

逃逸技術(shù)還有其他應(yīng)用,如

清除同步:線程同步的代價(jià)是降低了并發(fā)性和性能。逃逸分析可以判斷某個(gè)對(duì)象是否始終被一個(gè)線程訪問,若是的話,就可以撤銷對(duì)該對(duì)象的同步保護(hù),從而提高并發(fā)程度和性能。

矢量替代:逃逸分析方法如果發(fā)現(xiàn)對(duì)象的內(nèi)存存儲(chǔ)結(jié)構(gòu)不需要連續(xù)進(jìn)行的話,就可以將對(duì)象的部分甚至全部都保存在CPU寄存器內(nèi),這樣能大大提高訪問速度。

TALB

JVM在內(nèi)存新生代Eden Space中開辟了一小塊線程私有的區(qū)域,稱作TLAB(Thread-local allocation buffer)。在Java程序中很多對(duì)象都是小對(duì)象且用過即丟,它們不存在線程共享也適合被快速GC,所以對(duì)于小對(duì)象通常JVM會(huì)優(yōu)先分配在TLAB上,并且TLAB上的分配由于是線程私有所以沒有鎖開銷。

總結(jié)

對(duì)象不在堆上分配主要的原因是堆是共享的,在堆上分配對(duì)象有鎖的開銷,且堆上的分配的對(duì)象內(nèi)存需要GC才能進(jìn)行回收。

5.方法區(qū)

方法區(qū)和Java堆一樣,是各個(gè)線程共享的內(nèi)存區(qū)域。它用于存儲(chǔ)已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量,即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。

方法區(qū)是一個(gè)相對(duì)穩(wěn)定的內(nèi)存區(qū), 因?yàn)樗娣诺氖穷愋托畔ⅲ?而類型信息在被加載到方法區(qū)中之后, 除了必要的連接和初始化, 一般不會(huì)有較大改動(dòng) 。一般情況下, JVM也不會(huì)卸載類型信息, 所以方法區(qū)也可以稱為JVM的靜態(tài)區(qū)。 一個(gè)類型的生命周期一般就是整個(gè)程序的生命周期。 一個(gè)JVM實(shí)例中只存在一個(gè)方法區(qū), 方法區(qū)中的所有類型數(shù)據(jù)被所有線程共享。

6.運(yùn)行時(shí)常量池

運(yùn)行時(shí)常量池是方法區(qū)的一部分。Class文件中除了有類的版本,字段,方法等,還有一項(xiàng)信息就是常量池,用于存放編譯期生成的各種字面量和符號(hào)引用,而這些內(nèi)容將會(huì)在類加載之后進(jìn)入方法區(qū)的運(yùn)行時(shí)常量。同時(shí),運(yùn)行期間也能將新的常量放入池中。

歡迎關(guān)注本人博客:https://allen-yu.com/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 所有的Java開發(fā)人員可能會(huì)遇到這樣的困惑?我該為堆內(nèi)存設(shè)置多大空間呢?OutOfMemoryError的異常到底...
    Java_Explorer閱讀 614評(píng)論 0 21
  • 第二部分 自動(dòng)內(nèi)存管理機(jī)制 第二章 java內(nèi)存異常與內(nèi)存溢出異常 運(yùn)行數(shù)據(jù)區(qū)域 程序計(jì)數(shù)器:當(dāng)前線程所執(zhí)行的字節(jié)...
    小明oh閱讀 1,275評(píng)論 0 2
  • 內(nèi)存溢出和內(nèi)存泄漏的區(qū)別 內(nèi)存溢出:out of memory,是指程序在申請(qǐng)內(nèi)存時(shí),沒有足夠的內(nèi)存空間供其使用,...
    Aimerwhy閱讀 800評(píng)論 0 1
  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,787評(píng)論 11 349
  • 一、運(yùn)行時(shí)數(shù)據(jù)區(qū)域 Java虛擬機(jī)管理的內(nèi)存包括幾個(gè)運(yùn)行時(shí)數(shù)據(jù)內(nèi)存:方法區(qū)、虛擬機(jī)棧、本地方法棧、堆、程序計(jì)數(shù)器,...
    kennethan閱讀 2,177評(píng)論 1 91

友情鏈接更多精彩內(nèi)容