Java內存模型,通常指的是JDK 5 開始使用的新的內存模型,主要由JSR-133: JavaTM Memory Model and Thread Specification 描述。html
JMM
就是一種符合內存模型規範的,屏蔽了各類硬件和操做系統的訪問差別的,保證了Java
程序在各類平臺下對內存的訪問都能保證效果一致的機制及規範。java
內存模型能夠理解爲在特定的操做協議下,對特定的內存或者高速緩存進行讀寫訪問的過程抽象,不一樣架構下的物理機擁有不同的內存模型,Java虛擬機也有本身的內存模型,即Java內存模型(Java Memory Model, JMM)。在C/C++語言中直接使用物理硬件和操做系統內存模型,致使不一樣平臺下併發訪問出錯。而JMM的出現,可以屏蔽掉各類硬件和操做系統的內存訪問差別,實現平臺一致性,是的Java程序可以「一次編寫,處處運行」。android
JMM主要解決的問題: 解決因爲多線程經過共享內存進行通訊時,存在的本地內存數據不一致、編譯器會對代碼指令重排序、處理器會對代碼亂序執行等帶來的問題算法
內存模型解決併發問題主要採用兩種方式:限制處理器優化和使用內存屏障數組
參考:緩存
傳送門:JAVA 虛擬機規範bash
HotSpot 白皮書: Memory Management in the Java HotSpot Virtual Machine 它描述了垃圾回收(GC)觸發的內存自動管理數據結構
其實 Java 虛擬機的內存結構並非官方的說法,在《Java 虛擬機規範》中用的是「運行時數據區(Run-Time Data Areas)」這個術語多線程
下圖能很清晰的說明JVM內存結構佈局:架構
JVM內存結構主要有三大塊:
在《深刻理解Java虛擬機(第二版)》中的描述是下面這個樣子的:
按照《JAVA 虛擬機規範》的中所述,能夠分爲公有和私有兩部分
公有部分: 堆[Heap]、方法區[Method Area]、常量池[Constant Pool]
私有部分: PC寄存器、VM虛擬機棧、本地方法棧
經過一張圖瞭解如何經過參數來控制各個區域的內存大小
參數說明:
-Xms:設置堆的最小空間大小。 -Xmx:設置堆的最大空間大小。 -XX:NewSize設置新生代最小空間大小。 -XX:MaxNewSize設置新生代最大空間大小。 -XX:PermSize設置永久代最小空間大小。 -XX:MaxPermSize設置永久代最大空間大小。 -Xss:設置每一個線程的堆棧大小。
沒有直接設置老年代的參數,可是能夠設置堆空間大小和新生代空間大小兩個參數來間接控制。
老年代空間大小=堆空間大小-年輕代大空間大小
Java堆是被全部線程共享的一塊內存區域,在虛擬機啓動時建立.
這塊區域專門用於 Java 實例對象和數組的內存分配,幾乎全部實例對象都在會這裏進行內存的分配
之因此說幾乎是由於有特殊狀況,有些時候小對象會直接在棧上進行分配,這種現象咱們稱之爲「棧上分配」
Java堆是垃圾收集器管理的主要區域,所以不少時候也被稱作「GC堆」。
若是從內存回收的角度看,因爲如今收集器基本都是採用的分代收集算法
,因此Java堆中還能夠細分
主要被劃分爲: 新生代「Young Generation
」和老年代「Old Generation
」
新生代「Young Generation
」又可分爲:Eden
、From Survivor 0
、To Survivor 1
根據《JAVA 虛擬機規範》的規定,Java堆能夠處於物理上不連續的內存空間中,只要邏輯上是連續的便可,就像咱們的磁盤空間同樣。在實現時,既能夠實現成固定大小的,也能夠是可擴展的,不過當前主流的虛擬機都是按照可擴展來實現的(經過-Xmx
和-Xms
控制)。
堆能夠具備固定大小,或者能夠根據計算的須要進行擴展,而且若是不須要更大的堆,則能夠收縮。
若是在堆中沒有內存完成實例分配,而且堆也沒法再擴展時,將會拋出OutOfMemoryError異常。
Eden
區域Survivor
區域Survivor
拷貝到Old Generation
當有對象須要分配時,一個對象永遠優先被分配在年輕代的 Eden
區,等到 Eden
區域內存不夠時,Java 虛擬機
會啓動垃圾回收。此時 Eden
區中沒有被引用的對象的內存就會被回收,而一些存活時間較長的對象則會進入到老年代。
在 JVM 中有一個名爲 -XX:MaxTenuringThreshold
的參數(默認爲7)專門用來設置晉升到老年代所須要經歷的 GC 次數,即在年輕代的對象通過了指定次數的 GC 後,將在下次 GC 時進入老年代
虛擬機中的對象必然有存活時間長的對象,也有存活時間短的對象,這是一個廣泛存在的正態分佈規律。若是咱們將其混在一塊兒,那麼由於存活時間短的對象有不少,那麼勢必致使較爲頻繁的垃圾回收。而垃圾回收時不得不對全部內存都進行掃描,但其實有一部分對象,它們存活時間很長,對他們進行掃描徹底是浪費時間。所以爲了提升垃圾回收效率
,分區就理所固然了
虛擬機Heap Space
的默認配置爲: Eden:from :to = 8:1:1
其實這是 IBM 公司根據大量統計得出的結果。根據 IBM 公司對對象存活時間的統計,他們發現 80% 的對象存活時間都很短。因而他們將 Eden 區設置爲年輕代的 80%,這樣能夠減小內存空間的浪費,提升內存空間利用率
方法區(Method Area)與Java堆同樣,是各個線程共享的內存區域,在虛擬機啓動時建立。
它用於存儲已被虛擬機加載的類結構,如:運行時常量池、靜態變量、字段、和方法數據,即時編譯器編譯後的代碼等數據,以及方法和構造函數的代碼
Java虛擬機規範對這個區域的限制很是寬鬆,除了和Java堆同樣不須要連續的內存和能夠選擇固定大小或者可擴展外,還能夠選擇不實現垃圾收集。相對而言,垃圾收集行爲在這個區域是比較少出現的,但並不是數據進入了方法區就如永久代的名字同樣「永久」存在了。這個區域的內存回收目標主要是針對常量池的回收和對類型的卸載,通常來講這個區域的回收「成績」比較難以使人滿意,尤爲是類型的卸載,條件至關苛刻,可是這部分區域的回收確實是有必要的。
根據《Java虛擬機規範的規定》,當方法區沒法知足內存分配需求時,將拋出OutOfMemoryError異常。
例如在 1.7
版本的 HotSpot 虛擬機
中,方法區被稱爲永久代(Permanent Space)
,而在 JDK 1.8
中則被稱之爲 MetaSpace
能夠看到常量池實際上是存放在方法區中的,但《Java 虛擬機規範》將常量池和方法區放在同一個等級上
雖然《Java虛擬機規範》把方法區描述爲堆的一個邏輯部分,可是它卻有一個別名叫作Non-Heap(非堆),目的應該是與Java堆區分開來。
對於習慣在HotSpot虛擬機上開發和部署程序的開發者來講,不少人願意把方法區稱爲「永久代」(Permanent Generation),本質上二者並不等價,僅僅是由於HotSpot虛擬機的設計團隊選擇把GC分代收集擴展至方法區,或者說使用永久代來實現方法區而已。
每一個Java虛擬機線程都有本身私有獨立
的 pc(程序計數器)寄存器。
它的做用能夠看作是當前線程所執行的字節碼的行號指示器。
在任什麼時候候,每一個Java虛擬機線程都在執行單個方法的代碼
,即該線程的當前方法。若是不是該方法 native,則pc寄存器包含當前正在執行的Java虛擬機指令的地址。若是線程當前正在執行native
方法,則Java虛擬機pc 寄存器的值爲undefined
。
此內存區域是惟一一個在Java虛擬機規範中沒有規定任何OutOfMemoryError狀況的區域
與程序計數器同樣,Java虛擬機棧(Java Virtual Machine Stack
)也是線程私有的,它的生命週期與線程相同,與線程同時建立。
JVM Stack
存儲幀(Stack Frame
) [A Java Virtual Machine stack stores frames]
JVM Stack
相似於傳統語言的Stack
,例如C語言:它保存局部變量和部分結果,並在方法調用和返回中起做用。因爲除了推送和彈出幀(Frames
)以外,永遠不會直接操做Java虛擬機堆棧(Java Virtual Machine Stack),所以幀(Frames)
能夠是堆(heap)
分配的。Java虛擬機堆棧的內存不須要是連續的。
虛擬機棧(JVM Stacks)
描述的是Java方法執行的內存模型:每一個方法被執行的時候都會同時建立一個棧幀(Stack Frame)
用於存儲局部變量表、操做數棧、動態連接、方法出口以及一些過程結果等信息。每個方法被調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中從入棧到出棧的過程。
局部變量表存放了編譯期可知的各類基本數據類型(boolean、byte、char、short、int、float、long、double)
、對象引用(reference類型
,它不等同於對象自己,根據不一樣的虛擬機實現,它多是一個指向對象起始地址的引用指針,也可能指向一個表明對象的句柄或者其餘與此對象相關的位置)和returnAddress類型
(指向了一條字節碼指令的地址)。
其中64位長度的long和double類型的數據會佔用2個局部變量空間(Slot),其他的數據類型只佔用1個。局部變量表所需的內存空間在編譯期間完成分配,當進入一個方法時,這個方法須要在幀中分配多大的局部變量空間是徹底肯定的,在方法運行期間不會改變局部變量表的大小。
在Java虛擬機規範中,對這個區域規定了兩種異常情況:若是線程請求的棧深度大於虛擬機所容許的深度,將拋出StackOverflowError
異常;若是虛擬機棧能夠動態擴展(當前大部分的Java虛擬機均可動態擴展,只不過Java虛擬機規範中也容許固定長度的虛擬機棧),當擴展時沒法申請到足夠的內存時會拋出OutOfMemoryError
異常。
本地方法棧(Native Method Stacks)與虛擬機棧所發揮的做用是很是類似的,其區別不過是虛擬機棧爲虛擬機執行Java方法(也就是字節碼)服務,而本地方法棧則是爲虛擬機使用到的Native方法服務。虛擬機規範中對本地方法棧中的方法使用的語言、使用方式與數據結構並無強制規定,所以具體的虛擬機能夠自由實現它。甚至有的虛擬機(譬如Sun HotSpot虛擬機
)直接就把本地方法棧和虛擬機棧合二爲一。與虛擬機棧同樣,本地方法棧區域也會拋出StackOverflowError和OutOfMemoryError
異常。