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

歡迎訪問(wèn) 生活随笔!

生活随笔

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

编程问答

《探索JVM内存区域》

發(fā)布時(shí)間:2024/3/26 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 《探索JVM内存区域》 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

      • 一、為什么要了解JVM內(nèi)存區(qū)域
      • 二、結(jié)識(shí)JVM中的“內(nèi)存”成員
        • 1. 程序計(jì)數(shù)器(PC=Program Counter Register)
        • 2. JVM棧(Java Virtual Machine Stacks)
        • 3. 本地方法棧(Native Method Stack)
        • 4. Java堆(Java Heap)
        • 5. 方法區(qū)(Method Area)
        • 6. 需要知道的一個(gè)“鄰居“–直接內(nèi)存
      • 三、HotSpot JVM的Java堆中的對(duì)象
        • 1. 對(duì)象的創(chuàng)建
        • 2. 對(duì)象在內(nèi)存中存儲(chǔ)的布局
        • 3. 對(duì)象的訪問(wèn)定位

一、為什么要了解JVM內(nèi)存區(qū)域

? ?? ?對(duì)于C/C++程序員,它們使用對(duì)象時(shí)既要先使用new新建對(duì)象,又要在使用完對(duì)象之后free或delete掉對(duì)象。這樣不僅辛苦,而且容易導(dǎo)致以后忘記釋放掉對(duì)象所占的內(nèi)存空間。而Java則采用了比較自動(dòng)化式的機(jī)制,將最后的“對(duì)象銷(xiāo)毀”操作交由JVM自動(dòng)完成,使得我們使用對(duì)象時(shí)只需通過(guò)new新建一個(gè)對(duì)象即可使用,完全不用擔(dān)心以后會(huì)忘記釋放對(duì)象所占的內(nèi)存空間。

??? ?如果是滿足日常的學(xué)習(xí)需要,使得自己編寫(xiě)的Java程序得以在自己的機(jī)器上順利運(yùn)行,則JVM提供的這種自動(dòng)的垃圾回收機(jī)制足矣,何況,目前商用的高性能JVM都已經(jīng)提供了相當(dāng)多的優(yōu)化特性和調(diào)節(jié)手段;但是,如果是在生產(chǎn)開(kāi)發(fā)環(huán)境下,則經(jīng)常會(huì)發(fā)生內(nèi)存泄漏(Memory Leak)內(nèi)存溢出(Memory Overflow)的情況,這時(shí)我們就需要借助JVM的內(nèi)存結(jié)構(gòu)來(lái)排查問(wèn)題了。



二、結(jié)識(shí)JVM中的“內(nèi)存”成員

研究JVM所管理的內(nèi)存時(shí),我們主要研究的是其中的運(yùn)行時(shí)數(shù)據(jù)區(qū)(一個(gè)“大家庭”)。這個(gè)大家庭由下面5個(gè)成員組成。


1. 程序計(jì)數(shù)器(PC=Program Counter Register)

  • 屬于”線程私有“的內(nèi)存,生命周期與線程一致。
  • 該區(qū)域是一塊較小的內(nèi)存空間,是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。在JVM的概念模型中,字節(jié)碼解釋器就是通過(guò)改變這個(gè)計(jì)數(shù)器的值來(lái)取出下一條將要執(zhí)行的字節(jié)碼指令。
  • 在多線程的使用環(huán)境下,基于CPU的時(shí)間片輪轉(zhuǎn)調(diào)度算法原理,為了實(shí)現(xiàn)線程切換后能恢復(fù)到正確的執(zhí)行位置(也就是在線程被掛起之前,記住線程已經(jīng)執(zhí)行到哪行字節(jié)碼,在線程恢復(fù)執(zhí)行之后,從之前記住的位置開(kāi)始繼續(xù)往下執(zhí)行),每條線程都需配備一個(gè)相互獨(dú)立的程序計(jì)數(shù)器,這類(lèi)內(nèi)存區(qū)域?qū)儆凇本€程私有“的內(nèi)存。


2. JVM棧(Java Virtual Machine Stacks)

  • 屬于”線程私有“的內(nèi)存,生命周期與線程一致。
  • 該區(qū)域描述了Java方法執(zhí)行時(shí)的內(nèi)存模型:每個(gè)方法在執(zhí)行的同時(shí)均會(huì)創(chuàng)建一個(gè)棧幀(Stack Frame)
    ,用以存放局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口等信息。每個(gè)方法從被調(diào)用到執(zhí)行完成,對(duì)應(yīng)著一個(gè)棧幀在JVM棧中的入棧到出棧過(guò)程。
  • 人們經(jīng)常提及JVM中的”堆“和”棧“,而其中的”棧“其實(shí)就是JVM棧,主要指的是JVM棧中的局部變量表。
  • 局部變量表存放了編譯期可知的各種基本數(shù)據(jù)類(lèi)型、對(duì)象引用和returnAddress類(lèi)型。
  • 在JVM規(guī)范中,對(duì)該區(qū)域規(guī)定了兩種異常:
    • SOF異常:線程申請(qǐng)的棧深度超過(guò)虛擬機(jī)所允許的棧深度。
    • OOM異常:可以動(dòng)態(tài)拓展的虛擬機(jī)進(jìn)行拓展時(shí)無(wú)法申請(qǐng)到足夠的內(nèi)存。


3. 本地方法棧(Native Method Stack)

  • 屬于”線程私有“的內(nèi)存,生命周期與線程一致。
  • 該區(qū)域?yàn)镴VM使用到的Native方法服務(wù),而前面的JVM棧為JVM使用到的Java方法服務(wù)。
  • JVM規(guī)范中沒(méi)有強(qiáng)制規(guī)定本地方法棧的實(shí)現(xiàn)方式,故具體的虛擬機(jī)可以自由實(shí)現(xiàn)它。
  • 其中,Sun HotSpot虛擬機(jī)直接將本地方法棧和JVM棧合二為一。
  • 在JVM規(guī)范中,該區(qū)域也有SOF、OOM異常。


4. Java堆(Java Heap)

  • 屬于”被所有線程共享“的內(nèi)存。
  • 該區(qū)域?yàn)镴VM所管理的內(nèi)存中最大的一塊內(nèi)存,在JVM啟動(dòng)時(shí)便創(chuàng)建。
  • 該區(qū)域用以存放幾乎所有的對(duì)象實(shí)例,JVM規(guī)范中提到:The heap is the runtime data area from which memory for all class instances and arrays is allocated(所有的對(duì)象實(shí)例以及數(shù)組都要在堆上分配)。但是隨著各種JVM技術(shù)的發(fā)展,就目前來(lái)說(shuō),”所有對(duì)象實(shí)例都在堆上分配“也變得不那么絕對(duì)了。
  • 該區(qū)域?yàn)槔占鞴芾淼闹饕獏^(qū)域,故又名”GC堆”(Garbage Collected Heap)。從內(nèi)存回收的角度看,基于JVM分代搜集算法,該區(qū)域可以分為新生代和老年代;從內(nèi)存分配的角度看,該區(qū)域可以劃分出多個(gè)線程私有的分配緩沖區(qū)(TLAB)。
  • 該區(qū)域的內(nèi)存空間可以物理不連續(xù),邏輯上連續(xù)即可。
  • 在JVM規(guī)范中,若該區(qū)域中已沒(méi)有內(nèi)存可以進(jìn)行對(duì)象實(shí)例分配,且無(wú)法再拓展時(shí),將產(chǎn)生OOM異常。


5. 方法區(qū)(Method Area)

  • 屬于”被所有線程共享“的內(nèi)存,別名”Non-Heap“。
  • 該區(qū)域用以存放已被JVM加載的類(lèi)信息(類(lèi)的名稱(chēng)、方法信息、字段信息)、常量、靜態(tài)變量、JIT編譯器編譯后的代碼等數(shù)據(jù)。
  • HotSpot虛擬機(jī)將GC分代收集拓展至方法區(qū),使用永久代來(lái)實(shí)現(xiàn)方法區(qū),這樣JVM的垃圾收集器可以像管理Java堆那樣管理這個(gè)區(qū)域,省去專(zhuān)門(mén)為該區(qū)域編寫(xiě)內(nèi)存管理代碼的工作。這使得很多使用HotSpot虛擬機(jī)進(jìn)行開(kāi)發(fā)的人習(xí)慣將該區(qū)域稱(chēng)為”永久代“(Permanent Generation)。
  • JVM規(guī)范中沒(méi)有規(guī)定該區(qū)域的實(shí)現(xiàn)形式,故具體的虛擬機(jī)可以自由實(shí)現(xiàn)它。
  • HotSpot虛擬機(jī)使用永久代來(lái)實(shí)現(xiàn)該區(qū)域,目前來(lái)看不是那么好,容易導(dǎo)致內(nèi)存溢出,因?yàn)橛谰么?XX:MaxPermSize的上限,且導(dǎo)致一些方法(如String.intern())在不同的JVM上表現(xiàn)不同。因此,在jdk1.7的HotSpot中,將原本放在永久代的字符串常量池移至Java堆中(官方原文:In JDK 7, interned strings are no longer allocated in the permanent generation of the Java heap, but are instead allocated in the main part of the Java heap (known as the young and old generations), along with the other objects created by the application. This change will result in more data residing in the main Java heap, and less data in the permanent generation, and thus may require heap sizes to be adjusted. )。在jdk1.8中移除整個(gè)永久代,使用元空間(Metaspace)來(lái)代替永久代實(shí)現(xiàn)方法區(qū)。而方法區(qū)就是用來(lái)存放一些描述性信息的,即元數(shù)據(jù)。所以這個(gè)”元空間“至少比之前的”永久代“來(lái)得更加形象和見(jiàn)名知意了。

(關(guān)于元空間可以參考這篇博文:Java 8: 從永久代(PermGen)到元空間(Metaspace))

  • 該區(qū)域還包含了一個(gè)很重要的區(qū)域–運(yùn)行時(shí)常量池 (Runtime Constant Pool)。Class文件中有一項(xiàng)常量池信息,記錄了編譯期生成的各種字面量和符號(hào)引用,當(dāng)Class文件被加載時(shí),這部分信息將被存放到方法區(qū)的運(yùn)行時(shí)常量池中。
  • JVM規(guī)范對(duì)Class文件中的每一部分(包括常量池)的格式均有嚴(yán)格規(guī)定,而對(duì)運(yùn)行時(shí)常量池則沒(méi)有任何細(xì)節(jié)要求,故具體的虛擬機(jī)可以自由實(shí)現(xiàn)它。
  • Class文件中的常量池與運(yùn)行時(shí)常量池,兩者最大的不同在于后者具備動(dòng)態(tài)性,因?yàn)镴ava不要求常量只有在編譯期才能產(chǎn)生,也就是在運(yùn)行期間也可以產(chǎn)生新的常量并置入運(yùn)行時(shí)常量池中(如String.intern()方法)。
  • 在JVM規(guī)范中,當(dāng)運(yùn)行時(shí)常量池?zé)o法再申請(qǐng)到內(nèi)存時(shí),將產(chǎn)生OOM異常。
  • 在JVM規(guī)范中,當(dāng)該區(qū)域無(wú)法滿足內(nèi)存分配需求時(shí),將產(chǎn)生OOM異常。


6. 需要知道的一個(gè)“鄰居“–直接內(nèi)存

  • 除了以上5個(gè)成員之外,其實(shí)還有一個(gè)我們經(jīng)常“拜訪”(頻繁使用)的鄰居–直接內(nèi)存(Direct Memory)。但他不屬于運(yùn)行時(shí)數(shù)據(jù)區(qū)這個(gè)大家庭,也不是Java虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域。
  • 直接內(nèi)存常被稱(chēng)為堆外內(nèi)存,自從JDK1.4引入NIO之后,使得我們也經(jīng)常和這個(gè)鄰居打交道了。我們可以通過(guò)Native方法分配直接內(nèi)存,然后通過(guò)DirectByteBuffer對(duì)象操作這部分內(nèi)存。這部分內(nèi)存如果使用不當(dāng),也會(huì)發(fā)生OOM異常。在配置虛擬機(jī)參數(shù)時(shí),設(shè)置-Xmx等參數(shù)時(shí)應(yīng)考慮到這部分內(nèi)存。



三、HotSpot JVM的Java堆中的對(duì)象

以下討論中的“對(duì)象“僅限于普通Java對(duì)象,不包括數(shù)組和Class對(duì)象等。

1. 對(duì)象的創(chuàng)建

  • JVM遇到一條new指令時(shí),先檢查new指令的參數(shù)是否能在運(yùn)行時(shí)常量池中定位到一個(gè)類(lèi)的符號(hào)引用,并檢查這個(gè)符號(hào)引用代表的類(lèi)是否已經(jīng)被加載、解析和初始化。若沒(méi)有,則先執(zhí)行相應(yīng)的類(lèi)加載過(guò)程。
  • 類(lèi)加載的檢查通過(guò)后,JVM將為新生對(duì)象分配相應(yīng)的內(nèi)存,對(duì)象所需的內(nèi)存大小在類(lèi)加載完成之后便可完全確定了,給對(duì)象分配內(nèi)存空間其實(shí)就是在Java堆中分出一塊確定大小的內(nèi)存。此時(shí),有兩種情況。若Java堆中的內(nèi)存是絕對(duì)規(guī)整的,用過(guò)的內(nèi)存和空閑的內(nèi)存各放一邊,中間放置一個(gè)指針作為分界點(diǎn)的指示器,此時(shí)分配內(nèi)存就是把指針向空閑的內(nèi)存那邊移動(dòng)一段與對(duì)象大小相等的距離,這種內(nèi)存分配方式稱(chēng)之為”指針碰撞(Bump the Pointer)“;若Java堆中的內(nèi)存并不是規(guī)整的,用過(guò)的內(nèi)存和空閑的內(nèi)存相互交錯(cuò),此時(shí)JVM會(huì)維護(hù)一個(gè)列表,記錄所有內(nèi)存塊的使用情況,在分配內(nèi)存時(shí)從列表中找出一塊足夠大的內(nèi)存塊分配給對(duì)象實(shí)例,并更新列表上的記錄,這種內(nèi)存分配方式稱(chēng)之為”空閑列表(Free List)“。
  • Java堆是否規(guī)整,與JVM采用的垃圾收集器是否帶有壓縮整理功能有關(guān)。
  • 為了解決關(guān)于內(nèi)存分配的多線程并發(fā)的安全問(wèn)題,JVM有兩個(gè)方法:
    • 對(duì)分配內(nèi)存空間的動(dòng)作進(jìn)行同步處理;
    • 把內(nèi)存分配的動(dòng)作按線程劃分在不同的空間中進(jìn)行,也就是Java堆為每個(gè)線程預(yù)先分配一小塊內(nèi)存,稱(chēng)為本地線程分配緩沖(Thread Local Allocation Buffer,TLAB)。哪個(gè)線程要分配內(nèi)存,則先在其TLAB上進(jìn)行分配。當(dāng)TLAB用完并分配新的TLAB時(shí),才需要進(jìn)行同步鎖定。(其中,設(shè)置JVM是否使用TLAB,可以通過(guò) -XX:+/-UseTLAB參數(shù)設(shè)定)
  • 內(nèi)存分配完成后,JVM將分配出的內(nèi)存初始化為零值(對(duì)象頭除外),若使用TLAB,則此工作可以提前到TLAB分配時(shí)進(jìn)行。
  • 內(nèi)存初始化后,JVM將對(duì)象的相關(guān)信息(類(lèi)的元數(shù)據(jù)信息、所屬類(lèi)、哈希碼、GC分代年齡等信息)存放于對(duì)象的對(duì)象頭中。而且根據(jù)JVM的當(dāng)前運(yùn)行狀態(tài),對(duì)對(duì)象頭會(huì)有不同的設(shè)置方式。
  • 最后,一般地(由字節(jié)碼中是否跟隨invokespecial指令決定),執(zhí)行new指令后會(huì)緊接著執(zhí)行<init>方法,將對(duì)象按照我們的意愿進(jìn)行初始化,這樣一個(gè)真正可用的對(duì)象才算誕生了。


2. 對(duì)象在內(nèi)存中存儲(chǔ)的布局

  • 對(duì)象在內(nèi)存中存儲(chǔ)的布局分為三部分:對(duì)象頭(Head)、實(shí)例數(shù)據(jù)(Instance Data)和對(duì)齊填充(Padding)。
  • 對(duì)象頭,分為兩部分信息

    • 第一部分:存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù)(如哈希碼、GC分代年齡、鎖狀態(tài)標(biāo)志、線程持有的鎖等),這部分?jǐn)?shù)據(jù)官方稱(chēng)它為”Mark Word”。其被設(shè)計(jì)成一個(gè)非固定的數(shù)據(jù)結(jié)構(gòu)以便在極小的空間中存儲(chǔ)盡量多的信息,它會(huì)根據(jù)對(duì)象的狀態(tài)復(fù)用自己的存儲(chǔ)空間。


    • 第二部分:類(lèi)型指針,即對(duì)象指向它的類(lèi)元數(shù)據(jù)的指針,JVM通過(guò)該指針確定對(duì)象是哪個(gè)類(lèi)的實(shí)例。(注意:不是所有的JVM實(shí)現(xiàn)都必須在對(duì)象數(shù)據(jù)上保留類(lèi)型指針,即查看對(duì)象的類(lèi)元數(shù)據(jù)信息并非要經(jīng)過(guò)對(duì)象本身)


    • 若對(duì)象是一個(gè)數(shù)組,則對(duì)象頭中還需配備一塊存儲(chǔ)數(shù)組長(zhǎng)度值的內(nèi)存,因?yàn)镴VM無(wú)法從數(shù)組的元數(shù)據(jù)信息中獲取數(shù)組的長(zhǎng)度,但是對(duì)于普通Java對(duì)象則可借助其元數(shù)據(jù)信息確定其所占內(nèi)存大小。


  • 實(shí)例數(shù)據(jù)

存儲(chǔ)關(guān)于對(duì)象的有效信息,即代碼中定義的各種類(lèi)型字段的內(nèi)容,包括父類(lèi)繼承下來(lái)的和子類(lèi)中定義的字段。這部分信息的存儲(chǔ)順序與JVM的分配策略參數(shù)(FieldsAllocationStyle)和字段在Java源代碼中的定義順序有關(guān)。



  • 對(duì)齊填充

該部分不是必然存在的,僅起著占位符的作用。因?yàn)镠otSpot JVM的自動(dòng)內(nèi)存管理系統(tǒng)要求對(duì)象的起始地址必須是8字節(jié)的整數(shù)倍,也就是對(duì)象的大小必須是8字節(jié)的整數(shù)倍。而對(duì)象頭部分恰為8字節(jié)的整數(shù)倍(1或2倍),故當(dāng)對(duì)象實(shí)例數(shù)據(jù)部分沒(méi)有對(duì)齊時(shí),需通過(guò)對(duì)齊填充來(lái)對(duì)齊補(bǔ)全。


3. 對(duì)象的訪問(wèn)定位

我們?cè)谑褂脤?duì)象時(shí),其實(shí)是通過(guò)JVM棧上的reference數(shù)據(jù)來(lái)操作Java堆上的對(duì)象實(shí)例。而JVM規(guī)范中沒(méi)有規(guī)定

reference數(shù)據(jù)(一個(gè)指向?qū)ο蟮囊?#xff09;應(yīng)該用哪種方式去定位和訪問(wèn)Java堆中的對(duì)象實(shí)例,故對(duì)象訪問(wèn)方式取決于具體的JVM實(shí)現(xiàn)。目前常用的有以下兩種實(shí)現(xiàn)方式:

  • 使用句柄訪問(wèn)

一個(gè)對(duì)象實(shí)例表現(xiàn)為兩部分:在Java堆中的對(duì)象實(shí)例數(shù)據(jù)和在方法區(qū)中的對(duì)象類(lèi)型數(shù)據(jù)。在Java堆中分出一塊內(nèi)存作為句柄池,reference數(shù)據(jù)存儲(chǔ)著對(duì)象實(shí)例的相應(yīng)句柄地址。而每個(gè)句柄中都包含著兩個(gè)指針:指向?qū)ο髮?shí)例數(shù)據(jù)的指針和指向?qū)ο箢?lèi)型數(shù)據(jù)的指針。其中對(duì)象實(shí)例數(shù)據(jù)存儲(chǔ)在Java堆中的實(shí)例池中,對(duì)象類(lèi)型數(shù)據(jù)存儲(chǔ)在方法區(qū)中。



  • 使用直接指針訪問(wèn)

一個(gè)對(duì)象實(shí)例在Java堆中表現(xiàn)為兩部分:指向?qū)ο箢?lèi)型數(shù)據(jù)的指針和對(duì)象的實(shí)例數(shù)據(jù)。reference數(shù)據(jù)存儲(chǔ)著對(duì)象實(shí)例的地址。其中對(duì)象類(lèi)型數(shù)據(jù)存儲(chǔ)在方法區(qū)中。




????第一種方式使得reference中存儲(chǔ)的是穩(wěn)定的句柄地址,即使以后對(duì)象實(shí)例的地址改變了,也只需改變句柄中的指向?qū)ο髮?shí)例數(shù)據(jù)的指針,reference本身無(wú)需改變。
????第二種方式加快了訪問(wèn)速度,因?yàn)楣?jié)省了一次指針定位的時(shí)間消耗。而且對(duì)象訪問(wèn)是頻繁發(fā)生的事件,因此這類(lèi)開(kāi)銷(xiāo)積少成多之后執(zhí)行成本也很可觀。HotSpot JVM就是采用這種方式進(jìn)行對(duì)象訪問(wèn)的。









參考資料:《深入理解Java虛擬機(jī)》、莫樞(撒迦)的《JVM分享》PPT

總結(jié)

以上是生活随笔為你收集整理的《探索JVM内存区域》的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

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