Java內(nèi)存區(qū)域——堆,棧,方法區(qū)等

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

  • jdk1.7中, Java虛擬機(jī)在執(zhí)行Java程序的過(guò)程中會(huì)把它所管理的內(nèi)存劃分為若干個(gè)不同的數(shù)據(jù)區(qū)域。
運(yùn)行時(shí)數(shù)據(jù)區(qū)域.png
數(shù)據(jù)區(qū)域。.png

二 線程私有內(nèi)存

2.1 程序計(jì)數(shù)器

    1. 程序計(jì)數(shù)器(Program Counter Register)是一塊較小的內(nèi)存空間,它可以看做是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。字節(jié)碼解釋器工作時(shí)就是通過(guò)改變這個(gè)計(jì)數(shù)器的值來(lái)選取下一條需要執(zhí)行的字節(jié)碼指令。
    1. 字節(jié)碼指令、分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等基礎(chǔ)功能都要依賴(lài)這個(gè)計(jì)數(shù)器來(lái)完成。
    1. 每條線程都有一個(gè)獨(dú)立的程序計(jì)數(shù)器,各條線程之間計(jì)數(shù)器互不影響,獨(dú)立存儲(chǔ)。如上圖所示,我們稱(chēng)這類(lèi)內(nèi)存區(qū)域?yàn)?: 線程私有內(nèi)存。
    1. 如果線程正在執(zhí)行的是一個(gè)Java方法,這個(gè)計(jì)數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址;如果正在執(zhí)行的是Native方法,這個(gè)計(jì)數(shù)器值則為空(Undefined)。
    1. 此內(nèi)存區(qū)域是唯一一個(gè)在Java虛擬機(jī)中沒(méi)有規(guī)范任何OutOfMemoryError情況的區(qū)域。

2.2 Java虛擬機(jī)棧

  • 每個(gè) Java 方法在執(zhí)行的同時(shí)會(huì)創(chuàng)建一個(gè)棧幀用于存儲(chǔ)局部變量表、操作數(shù)棧、常量池引用等信息。每一個(gè)方法從調(diào)用直至執(zhí)行完成的過(guò)程,就對(duì)應(yīng)著一個(gè)棧幀在 Java 虛擬機(jī)棧中入棧和出棧的過(guò)程。


    虛擬機(jī)棧.png
  • 該區(qū)域可能拋出以下異常:
    當(dāng)線程請(qǐng)求的棧深度超過(guò)最大值,會(huì)拋出 StackOverflowError 異常;
    棧進(jìn)行動(dòng)態(tài)擴(kuò)展時(shí)如果無(wú)法申請(qǐng)到足夠內(nèi)存,會(huì)拋出 OutOfMemoryError 異常。
    1. Java虛擬機(jī)棧也是線程私有的,它的生命周期與線程相同(隨線程而生,隨線程而滅)
    1. 如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度,將拋出StackOverflowError異常;如果虛擬機(jī)??梢詣?dòng)態(tài)擴(kuò)展,如果擴(kuò)展時(shí)無(wú)法申請(qǐng)到足夠的內(nèi)存,就會(huì)拋出OutOfMemoryError異常;(當(dāng)前大部分JVM都可以動(dòng)態(tài)擴(kuò)展,只不過(guò)JVM規(guī)范也允許固定長(zhǎng)度的虛擬機(jī)棧)
    1. Java虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個(gè)方法執(zhí)行的同時(shí)會(huì)創(chuàng)建一個(gè)棧幀。 對(duì)于我們來(lái)說(shuō),主要關(guān)注的stack棧內(nèi)存,就是虛擬機(jī)棧中局部變量表部分。
可以通過(guò) -Xss 這個(gè)虛擬機(jī)參數(shù)來(lái)指定一個(gè)程序的 Java 虛擬機(jī)棧內(nèi)存大?。?java -Xss=512M HackTheJava

2.3 棧幀

  • 棧幀是用于支持虛擬機(jī)進(jìn)行方法調(diào)用方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu),它是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)中的虛擬機(jī)棧的棧元素。
  • 棧幀用于存儲(chǔ)局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法返回等信息。
  • 每個(gè)方法從調(diào)用直至執(zhí)行完成的過(guò)程,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中入棧到出棧的過(guò)程。
  • 在活動(dòng)線程中,只有位于棧頂?shù)臈攀怯行У模Q(chēng)為當(dāng)前棧幀,與這個(gè)棧幀相關(guān)聯(lián)的方法稱(chēng)為當(dāng)前方法。執(zhí)行引擎運(yùn)行的所有字節(jié)碼指令都只針對(duì)當(dāng)前棧幀進(jìn)行操作。
  • 局部變量表存放了編譯期可知的各種基本數(shù)據(jù)類(lèi)型(boolean、byte、char、short、int、float、long、double)「String是引用類(lèi)型」,對(duì)象引用(reference類(lèi)型)和returnAddress類(lèi)型(它指向了一條字節(jié)碼指令的地址)
  • 局部變量存儲(chǔ)在局部變量表中,隨著線程而生,線程而滅。并且線程間數(shù)據(jù)不共享。
  • 但是,如果是成員變量,或者定義在方法外對(duì)象的引用,它們存儲(chǔ)在堆中。因?yàn)樵诙阎校蔷€程共享數(shù)據(jù)的,并且棧幀里的命名就已經(jīng)清楚的劃分了界限 : 局部變量表!
棧幀.png

2.4 本地方法棧

  • 本地方法棧(Native Method Stack)與虛擬機(jī)棧所發(fā)揮的作用是非常相似的,它們之間的區(qū)別不過(guò)是虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法服務(wù)(也就是字節(jié)碼)服務(wù),而本地方法棧為虛擬機(jī)使用到的Native方法服務(wù)。
  • Java虛擬機(jī)規(guī)范對(duì)本地方法棧使用的語(yǔ)言、使用方法與數(shù)據(jù)結(jié)構(gòu)并沒(méi)有強(qiáng)制規(guī)定,因此可以由虛擬機(jī)自由實(shí)現(xiàn)。例如:HotSpot虛擬機(jī)直接將本地方法棧和虛擬機(jī)棧合二為一。
  • 同虛擬機(jī)棧相同,Java虛擬機(jī)規(guī)范對(duì)這個(gè)區(qū)域也規(guī)定了兩種異常情況StackOverflowError 和 OutOfMemoryError異常。


    JNIFigure1.gif

三 線程共享內(nèi)存

3.1 堆

    1. Java堆是被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建,是虛擬機(jī)所管理的內(nèi)存中最大的一塊。此內(nèi)存區(qū)域的唯一目的就是【存放對(duì)象實(shí)例和數(shù)組】,幾乎所有的對(duì)象實(shí)例和數(shù)組都在這里分配內(nèi)存。
    1. Java堆是垃圾收集器管理的主要區(qū)域,也稱(chēng)為GC 垃圾堆。后面會(huì)專(zhuān)門(mén)分析GC算法。 從內(nèi)存回收的角度看,由于現(xiàn)在收集器基本都采用分代收集算法,所以Java堆可以細(xì)分為:新生代、老生代;
      從內(nèi)存分配的角度看,線程共享的Java堆可能劃分出多個(gè)線程私有的分配緩沖區(qū)(TLAB);
      不論如何劃分,都與存放的內(nèi)容無(wú)關(guān),無(wú)論哪個(gè)區(qū)域,存儲(chǔ)的仍然是對(duì)象實(shí)例和數(shù)組。
    1. 如果在堆中沒(méi)有內(nèi)存完成實(shí)例分配,并且堆上也無(wú)法再擴(kuò)展時(shí),將會(huì)拋出OutOfMemoryError異常。
  • 是垃圾收集的主要區(qū)域("GC 堆"),現(xiàn)代的垃圾收集器基本都是采用分代收集算法,該算法的思想是針對(duì)不同的對(duì)象采取不同的垃圾回收算法,因此虛擬機(jī)把 Java 堆分成以下三塊:
  • 新生代(Young Generation)
  • 老年代(Old Generation)
  • 永久代(Permanent Generation)
  • 當(dāng)一個(gè)對(duì)象被創(chuàng)建時(shí),它首先進(jìn)入新生代,之后有可能被轉(zhuǎn)移到老年代中。新生代存放著大量的生命很短的對(duì)象,因此新生代在三個(gè)區(qū)域中垃圾回收的頻率最高。為了更高效地進(jìn)行垃圾回收,把新生代繼續(xù)劃分成以下三個(gè)空間:
  • Eden
  • From Survivor
  • To Survivor


    ppt_img.gif
  • Java 堆不需要連續(xù)內(nèi)存,并且可以動(dòng)態(tài)增加其內(nèi)存,增加失敗會(huì)拋出 OutOfMemoryError 異常。
  • 可以通過(guò) -Xms 和 -Xmx 兩個(gè)虛擬機(jī)參數(shù)來(lái)指定一個(gè)程序的 Java 堆內(nèi)存大小,第一個(gè)參數(shù)設(shè)置初始值,第二個(gè)參數(shù)設(shè)置最大值。
    java -Xms=1M -Xmx=2M HackTheJava
    1. 內(nèi)存泄露和內(nèi)存溢出
  • 內(nèi)存泄露 : 指程序中動(dòng)態(tài)分配內(nèi)存給一些臨時(shí)對(duì)象,但是對(duì)象不會(huì)被GC所回收,它始終占用內(nèi)存。即被分配的對(duì)象可達(dá)但已無(wú)用,可用內(nèi)存越來(lái)越少。
  • 內(nèi)存溢出 : 指程序運(yùn)行過(guò)程中無(wú)法申請(qǐng)到足夠的內(nèi)存而導(dǎo)致的一種錯(cuò)誤。內(nèi)存溢出通常發(fā)生于老年代或永久代垃圾回收后,仍然無(wú)內(nèi)存空間容納新的Java對(duì)象的情況。
    內(nèi)存泄露是內(nèi)存溢出的一種誘因,不是唯一因素。

3.2 方法區(qū)

    1. 方法區(qū)又被稱(chēng)為靜態(tài)區(qū),是程序中永遠(yuǎn)唯一的元素存儲(chǔ)區(qū)域。和堆一樣,是各個(gè)線程共享的內(nèi)存區(qū)域。它用于存儲(chǔ)已被虛擬機(jī)加載的類(lèi)信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。
    1. Java虛擬機(jī)規(guī)范對(duì)方法區(qū)的限制非常寬松,除了和Java堆一樣 不需要連續(xù)的內(nèi)存和可以選擇固定大小或者可擴(kuò)展之外,還可以選擇不實(shí)現(xiàn)垃圾回收。
      這區(qū)域的內(nèi)存回收目標(biāo)主要是針對(duì)常量池的回收和類(lèi)型的卸載,一般而言,這個(gè)區(qū)域的內(nèi)存回收比較難以令人滿(mǎn)意,尤其是類(lèi)型的回收,條件相當(dāng)苛刻,但是這部分區(qū)域的內(nèi)存回收確實(shí)是必要的。
    1. 很多開(kāi)發(fā)者更愿意把方法區(qū)稱(chēng)為“永久代”(Perm Gen)(Permanent Generation)「總是存放不會(huì)輕易改變的內(nèi)容」。在目前已經(jīng)發(fā)布的JDK 1.7 的HotSpot中,已經(jīng)把原本放在永久代的字符串常量池移至堆中。
    1. 運(yùn)行時(shí)常量池(Runtime Constant Pool)是方法區(qū)的一部分。
  • JDK 1.8 中,已經(jīng)沒(méi)有方法區(qū)(永久代),而是將方法區(qū)直接放在一個(gè)與堆不相連的本地內(nèi)存區(qū)域(Native Memory),這個(gè)區(qū)域被叫做元空間。

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

    1. Class文件中除了有類(lèi)的版本、字段、方法、接口等描述信息外,還有一項(xiàng)信息是常量池(Constant Pool Table),用于存放編譯期生成的字面量和符號(hào)引用,這部分內(nèi)容(也可以稱(chēng)為 .Class文件中的靜態(tài)常量池)將在類(lèi)加載后進(jìn)入方法區(qū)的運(yùn)行時(shí)常量池中存放。
  • 字面量 : 比較接近Java語(yǔ)言層面的常量概念,如文本字符串、聲明為final的常量值等。(final修飾的成員變量和類(lèi)變量!「類(lèi)變量即靜態(tài)(成員)變量)」,也就是除final修飾的局部變量。
  • 符號(hào)引用* : 屬于編譯原理方面的概念,包括
    1.類(lèi)和接口的全限定名(即路徑,包名+類(lèi)名)。
    2.字段的名稱(chēng)和描述符。
    3.方法的名稱(chēng)和描述符.
  • 當(dāng)虛擬機(jī)運(yùn)行時(shí),需要從常量池獲得對(duì)應(yīng)的符號(hào)引號(hào),再在類(lèi)創(chuàng)建或運(yùn)行時(shí)解析、翻譯到具體的內(nèi)存地址之中(直接引用)。
    1. 除了保存Class文件中描述的符號(hào)引用外,還會(huì)把編譯出來(lái)的直接引用也存儲(chǔ)在運(yùn)行時(shí)常量池中。
    1. Java語(yǔ)言并不要求常量一定只有編譯期才能生成,也就是并非置入Class文件中常量池的內(nèi)容才能進(jìn)入方法區(qū)運(yùn)行時(shí)常量池,運(yùn)行期間也可能將新的常量放入池中,這種特性被開(kāi)發(fā)人員利用得比較多的便是String 類(lèi)的intern()方法。(后面會(huì)分析String類(lèi)。)
    1. 當(dāng)常量池?zé)o法再申請(qǐng)到內(nèi)存時(shí)也會(huì)拋出OutofMemoryError異常。

4 成員變量與局部變量

成員變量 : 方法外部,類(lèi)內(nèi)部定義的變量;
局部變量 : 方法或語(yǔ)句塊內(nèi)部定義的變量,必須初始化。
形參是局部變量,實(shí)參則可能是方法中的局部變量或全局變量。
棧內(nèi)存中的局部變量隨方法而生,隨方法而滅。
成員變量存儲(chǔ)在堆中的對(duì)象里,由垃圾收集器回收。

變量.png

參考

Java內(nèi)存區(qū)域——堆,棧,方法區(qū)等

最后編輯于
?著作權(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),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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