個人全部文章同步更新與Github--Java-Notes,想了解JVM,HashMap源碼分析,spring相關,劍指offer題解(Java版),能夠點個star。能夠看個人github主頁,天天都在更新喲。html
邀請您跟我一同完成 repojava
Java內存區域(內存結構)是面試的時候,問到JVM相關必定會問的東西,可是不少人有些概念是搞混的git
閱讀這篇文章前,一個重要的概念要弄清楚。github
Java 內存區域和 Java 內存模型(JMM)是兩個概念面試
不少人,包括寫博客的一些人,他們本身都搞不清楚這兩個啥意思,我搜出來文章不少將二者混爲一談的。算法
我用這個名字作標題也是讓不少不太清楚的朋友都能搜到這篇文章,而後搞清楚概念。這篇文章講 Java內存區域,若是你是想了解內存模型的話,能夠看個人這篇文章 Java內存模型。固然若是你是面試的話,通常是問你Java內存區域(JVM運行時數據區),因此我先寫這一篇文章spring
Java內存區域是這樣的:數據結構
內存模型是這樣的多線程
引用《深刻理解Java虛擬機》裏的原話:"這裏所講的主內存、工做內存與本書第2章所講的Java內存區域中的堆,棧、方法區等並非同一層次的內存劃分,這二者基本上是沒有關係的,若是二者必定要勉強對應起來,那從變量、主內存、工做內存的定義來看,主內存主要對應於Java堆中的對象實例數據部分,而工做內存則對應於虛擬機棧中的部分區域"函數
Java運行時數據區能夠分爲兩類:
java虛擬機棧是線程私有的,他的生命週期和線程相同。他描述的是Java方法執行的內存模型:每一個方法在執行的同時都會建立一個棧幀用於存儲局部變量表、操做數棧、動態連接、方法出口等信息每一個方法從調用知道執行完的過程,就對應這一個棧幀在虛擬機棧中入棧到出棧的過程。
什麼意思呢?我也用他的這個調用方法入棧的方式給你講吧
他存放了如下重要信息(部分):
咱們還記得上面圖的內存模型嗎?咱們把Java線程單獨拿出來,他的數據結構就成了這個樣子
假設咱們如今有一個遞歸程序
public class Test {
public static void main(String[] args) {
Test test = new Test();
test.recursive();
}
public void recursive(){
recursive();
}
}
複製代碼
如今他在main
線程中執行,那麼你能夠把上圖的當前線程替換成main
線程,而後他每調用一次recursive
函數,他就把它壓入棧中。若是當前執行方法完,那麼他的棧幀就要出棧被拋棄。
上面的不會執行完,他會一直調用,直到消耗棧容許的最大深度,而後拋出
Stack Overflow
異常,這個我在後面異常的時候會講到
咱們還聽到不少博客講述這個區域的時候有用到 slot這個概念,那個這個slot
是什麼呢?
咱們剛剛說到棧幀的數據結構,他存儲的重要信息,其中有一個是局部變量表,而這個局部變量表也是由一個個小的數據結構組成,他就叫局部變量空間(slot)。你也能夠把它當成是一個空間大小的度量單位,就像字節同樣,由於存儲的數據的大小也是用這個衡量的。
局部變量表存放了編譯期可知的各類基本數據類型(boolean、byte、char、short、int、float、long、double)、對象引用(reference類型)和returnAddress類型。(我不知道書上是否是寫錯了,由於下圖的本地變量表中並無這個類型,並且在上圖的棧幀中,也是把這個和局部變量表分開列出的,若是知道答案的請給我留言,很是感謝)
局部變量表,就以下圖所示(這個是對象的訪問定位的其中一種方法,指針直接訪問對象,Java採用的是這種)。若是要看Java對象訪問的話,能夠參考個人這篇文章,Java對象訪問。
咱們看到 int
,short
等只佔用了一個 slot
,可是double佔用了兩個slot,(long沒有畫出,但他也佔用兩個slot)在java虛擬機中確實是這樣,double和long類型比較特殊,在其餘方面,存儲long和double的時候,別的數據通常使用一個單位的空間就行,他們就須要兩個。
虛擬機棧會拋出兩種異常
這兩種異常是從兩個方面說的,能夠形象的說爲 深度和寬度
還記得上面棧幀的圖和個人程序嗎?
當我啓動那個程序他必定會報出 Stack Overflow Error。 由於他是無限的遞歸,可是個人虛擬機的深度是有限的(這個能夠進行參數設置調整 分配參數:-Xss)。順便說一句,若是你要運行不少的線程,那麼你可能須要把棧的深度調小,由於總的就那麼大,而每一個線程使用的是獨立大小
OOM異常的話,是因爲若是虛擬機棧能夠動態拓展的話(大部分均可以擴展),若是拓展時沒法申請到足夠的空間,那麼就會拋出OOM異常
注意:有的虛擬機中是將本地方法棧和虛擬機棧合在一塊兒的,好比Hotspot虛擬機
本地方法棧和虛擬機棧差很少,不過 Java虛擬機棧爲虛擬機執行Java方法服務,而本地方法棧爲虛擬機使用到的Native方法服務。咱們知道Java虛擬機有一部分是用其餘語言編寫的,好比C/C++,所以他有一部分是這些語言的類庫方法。
Navtive 方法是 Java 經過 JNI 直接調用本地 C/C++ 庫,能夠認爲是 Native 方法至關於 C/C++ 暴露給 Java 的一個接口,Java 經過調用這個接口從而調用到 C/C++ 方法。當線程調用 Java 方法時,虛擬機會建立一個棧幀並壓入 Java 虛擬機棧。然而當它調用的是 native 方法時,虛擬機會保持 Java 虛擬機棧不變,也不會向 Java 虛擬機棧中壓入新的棧幀,虛擬機只是簡單地動態鏈接並直接調用指定的 native 方法。
具體能夠參考這篇博文 Java本地方法棧
由於和Java虛擬機棧相似,因此異常也是
程序計數器是線程私有的,他是一塊較小的內存空間,他能夠看做是當前線程執行的字節碼的行號指令器。在虛擬機的概念模型裏(僅僅是概念模型,不一樣虛擬機的實現方式可能有更高效的方法),字節碼解釋器工做時就是經過改變這個計數器的值來選取下一條須要執行的字節碼指令,分支、循環、跳轉、異常處理等基礎功能都須要依賴這個計數器。
Java虛擬機的多線程是經過線程輪流切換並分配處理器執行時間的方式來實現的,在任何一個肯定的時刻,一個處理器(對於多核處理器來講是一個內核)都只會執行一條線程中的指令、所以爲了線程切換後能恢復到正確的執行位置,每條線程都須要有一個獨立的程序計數器,各條線程之間計數器互不影響,獨立存儲,咱們稱這類內存區域爲"線程私有"的內存。
他是Java虛擬機規範中惟一一個沒有規定OOM異常狀況的區域
這裏是GC垃圾回收重點"照顧"的對象,能夠參考個人博文:JVM垃圾回收
對於大所數應用來講,Java堆是Java虛擬機所管理的內存中最大的一塊,也被線程共享。惟一的目的就是存放對象實例,幾乎全部的對象實例都在這裏分配內存
他還能夠再細分爲:新生代,老年代(基於分代垃圾回收算法,能夠參考我上面的博文鏈接)
能夠經過參數設置大小(-Xmx 和 -Xms)
和Java堆同樣是線程共享的,它用於存儲已經被虛擬機加載的:
使用 Hotspot 虛擬機的開發者來講,更習慣把方法區稱爲"永久代",可是二者並不等價,僅僅是Hotspot虛擬機的設計團隊選擇把GC分代收集擴展到方法區。並且**永久代已經從JDK1.7開始逐漸移除,並在 JDK1.8以後徹底被移除。**並且Hotspot之因此有這樣的狀況是由於當時他們爲了方便,懶得爲方法區單獨寫內存管理算法,而直接使用Java堆的管理算法。
而後他們就爲這個付出了代價,因此如今已經移除了這個永久代
當方法區沒法知足內存分配需求
java內存區域(或者成爲內存結構或者運行時數據區)和java內存模型(JMM)不是同一個概念
內存區域劃分
線程私有
線程共享