Java&Android 基礎知識梳理(3) 內存區域

1、概述

Java虛擬機在執行Java程序的過程當中會把它所管理的內存劃分爲若干個不一樣的區域,它們有的隨着虛擬機進程的啓動而存在,有些區域則依賴用戶線程的啓動和結束而創建而銷燬。 下面,咱們就分兩個部分討論:程序員

  • 線程隔離的數據區
  • 全部線程共享的數據區

2、線程隔離的數據區

2.1 程序計數器

  • 概念 程序計數器是當前線程所執行的字節碼的行號指示器。字節碼解釋器工做時會經過改變這個計數器的指來取下一條須要執行的字節碼指令。 若是線程正在執行的是Java方法,那麼計數器記錄的是正在執行的虛擬機字節碼指令的地址;若是正在執行的是Native方法,那這個計數器則爲空。 此內存區域是惟一一個在Java虛擬機規範中沒有規定任何OOM狀況的區域。
  • 爲何須要線程隔離 因爲Java虛擬機的多線程是經過線程輪流切換並分配處理器執行時間的方式來實現的,所以,爲了線程切換後能恢復到正確的執行位置,每條線程都須要有一個獨立的程序計數器。

2.2 Java虛擬機棧

  • 概念 虛擬機棧描述的是Java方法執行的內存模型,每一個方法在執行的同時會建立一個棧幀,用於存儲局部變量表、操做數棧、動態連接、方法出口等信息。 局部變量表:存放了編譯期可知的各類基本數據類型(boolean/byte/..),對象引用(指向對象起始地址的引用指針,或者是指向一個表明對象的句柄,或者是其它與此對象相關的位置)和returnAddress地址。 局部變量表所需的內存空間在編譯期間完成分配,當進入一個方法時,這個方法須要在棧中分配多大局部變量空間是徹底肯定的,在方法運行期間不會改變局部變量表的大小。 每個方法從調用到執行完成的過程,就對應一個棧幀在虛擬機棧中出棧到入棧的過程。
  • 爲何須要線程隔離 由於每一個線程所執行的邏輯和時序不一樣,因此它們的虛擬機棧天然也就不會必定相同,所以不能共用。
  • 異常 若是線程請求的棧深度大於虛擬機所容許的深度,會拋出StackOverflowError異常;若是虛擬機棧能夠動態擴展,當擴展時沒法申請到足夠的內存,就會拋出OOM

2.3 本地方法棧

  • 概念 和Java虛擬機方法棧相似,不過本地方法棧爲虛擬機使用到的Native方法,有些虛擬機(譬如HotSpot)直接將本地方法棧和虛擬棧合二爲一。
  • 異常 和虛擬機棧相同。

3、線程共享的數據區

3.1 Java

  • 概念 Java堆在虛擬機啓動時建立,它的目的是存放對象實例,它也是垃圾收集器管理的主要區域。 Java堆能夠處於物理上不連續的內存空間中,只要邏輯上連續便可,在實現上,既能夠實現成固定大小的,也能夠是可擴展的。
  • 異常 若是在堆中沒有內存完成實例分配,而且堆也沒法再擴展時,會拋出OOM異常。

下面咱們討論一下堆中的對象分配、佈局和訪問過程數組

3.1.1 對象的建立

對象的建立分爲如下幾步:多線程

  • 第一步:當虛擬機遇到一條new指令時,首先將去檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,而且檢查這個符號引用表明的類是否已經被加載、解析和初始化過,若是沒有,那麼必須執行相應的類加載過程,
  • 第二步:接下來虛擬機將爲新生對象分配內存,對象所需內存的大小在類加載完成後即可肯定,分配的方式有兩種:
  • 指針碰撞:用過和空閒的內存以指針做爲分界點的指示器,分配內存就是把指針向空閒空間那邊挪動一個與對象大小相等距離,這種方式要求內存是規整的。
  • 空閒列表:維護一個列表,記錄哪些內存是可用的,在分配和回收時更新列表。

多線程的問題下的解決方案:佈局

  • 對分配內存空間的動做進行同步,虛擬機上採用CAS配上失敗重試的方式保證更新操做的原子性。線程

  • 每一個線程在Java堆中預先分配一小塊內存,成爲本地線程分配緩衝TLAB,哪一個線程須要分配內存,就在哪一個線程的TLAB上分配,只有TLAB用完須要分配新的TLAB才須要同步。指針

  • 第三步:在內存分配完成,把除了對象頭以外的分配到的內存空間都初始化爲零值,接下來就是對對象進行必要的設置,這些信息存放在對象頭中。code

  • 第四步:當對象頭設置完畢以後,從虛擬機的視角來看,新的對象就產生了,接着就執行<init>方法,把對象按照程序員的意願進行初始化。對象

3.1.2 對象的內存佈局

對象在內存中存儲的佈局能夠分爲三個區域:對象頭、實例數據、對其填充。接口

  • 對象頭進程

  • 存儲對象自身的運行時數據:HashCodeGc分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程ID、偏向時間戳等,所佔位數和虛擬機位數相同。

  • 類型指針:對象指向它的類元數據的指針,虛擬機經過這個指針來肯定這個對象是哪一個類的實例

  • 若是對象是一個Java數組,那麼在對象頭中還必須有一塊記錄數組長度的數據。

  • 實例數據 包括在父類和子類中所定義的各類類型的字段內容,存儲順序收到虛擬機分配策略參數的影響,相同寬度的字段老是被分配到一塊兒,在知足這個前提條件下,父類中定義的變量會出如今子類以前。

  • 對齊填充 HotSpot要求對象的大小必須是8字節的整數倍,而對象頭部分正好是8字節的整數倍,當對象實例數據部分沒有對齊時,就須要經過對齊填充來不全。

3.1.3 對象的訪問

對象的訪問有兩種方式:

  • 使用句柄,Java堆中劃分出一塊內存做爲句柄池,reference中存儲的就是對象的句柄地址,而句柄中包含了對象實例數據與類型數據各自的地址信息。 優勢:reference中存儲的是穩定的句柄地址,在對象被移動時,只會改變句柄中的實例數據指針,而reference自己不須要修改。
  • 直接指針:在Java堆對象的佈局中放置訪問類型數據的相關信息,reference中存儲的直接就是對象地址。 優勢:速度快,HotSpot採用的就是這種方式。

3.2 方法區

  • 概念 方法區用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。
  • 爲何把方法區稱爲「永久代」 HotSpot選擇把GC分代收集器擴展至方法區,或者說用永久代來實現方法區,這樣HotSpot的垃圾收集器能夠像管理Java堆同樣管理這部份內存,可以省去專門爲這個方法區編寫內存管理的代碼。 對這區域的內存回收主要是針對常量池的回收和對類型的卸載。
  • 異常 當方法區沒法知足內存分配需求時,將拋出OOM
  • 運行時常量池 運行時常量池是方法區的一部分。 Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池,用於存放編譯期生成的各類字面量和符號引用,這部份內容將在類加載後進入方法區的運行時常量池中存放。 並不是預置入Class文件中常量池的內容才能進入方法區運行時常量池,運行期間也可能將新的常量放入池中,例如String類的intern方法。 當常量池中沒法再申請內存,就會拋出OOM異常。
相關文章
相關標籤/搜索