標籤:連續 工作 stack javaee 佔用 並且 判斷 動態 共用
本篇文章帶來的是對Java記憶體資料模型的介紹,這對於我們深入理解Jvm虛擬機器工作的原理和Java記憶體的劃分大有裨益,好了,為了讓我們理解的更為深刻,我們將會加入圖片輔助的方法去理解。
本篇博文的目錄:
一:Java記憶體資料模型的介紹
二:線程私人記憶體
三:程式計數器
四:Java虛擬機器棧
五:本地方法棧
六:Java堆
七:方法區
八:運行時常量池
九:總結
一:java記憶體資料模型的介紹
java程式在啟動並執行時候會在記憶體中開闢不同的空間用以管理不用的記憶體地區,每個地區都有自己的功能,建立和銷毀時間,有的地區會隨著虛擬機器的啟動而建立,而有的地區會隨著使用者線程的建立而銷毀。按照空間,分為如下不同的空間,接下來我們將按照不同的地區進行學習,學習它不同地區的功能和用法
圖 1-1 :java記憶體資料模型
二:線程私人記憶體
我們先來看一下多線程運行原理:其實cpu在啟動並執行時候每次在同一特定的時間點只能運行一個線程(單核的情況下),只不過它切換的速度非常之快,讓我們覺的它是在進行多線程運行,本質上它還是單一啟動並執行。所以這就會必然引出一個問題:cpu切換線程如何保證它自身的運行不受其他的線程影響,保證每個線程都是獨立不受外部侵擾的,這就產生了線程私人記憶體這個概念,主要是維持線程的安全、穩定、高效的運轉。關於這一點也很好理解,比如我們去一個快速餐廳吃飯,我們點好了菜,會產生菜單小票,這個小票上面有編號,就是每個線程的“私人記憶體”,服務員再進行不同顧客上菜的時候就有了區分度,這樣就可以順利找到不同的顧客(切換不同的線程);在java線程中,程式計數器、虛擬機器棧、本地方法棧都屬於線程私人的。
注意點:線程私人不存在多線程並發的資源競爭問題,因為其享有的記憶體是互不影響的,不存在並發問題
與線程私人相對,就有線程公有記憶體,這個地區在jvm虛擬機器中有個特定的稱呼叫做:主記憶體
三:程式計數器:
對應於圖上標記的深紅色部分,主要是指程式在運行過程中所執行的位元組碼(.class)檔案的行號指示,也稱作行號指標,程式流程的分支、迴圈、異常處理等基礎功能都需要它的引導來完成。它佔有的記憶體空間比較小,它的啟動並執行原理是:通過改變計數器的值來選取下一條的需要執行的位元組碼命令,而這個值具體指的是虛擬機器在Java方法中位元組碼指令的地址,但是如果執行的是Native方法,那麼它的計數器的值是為空白的。
注意:
1:它是java中唯一一個沒有記憶體溢出(out of memory)情況的地區。可以思考一下這點是為什嗎?它是進行線程啟動並執行指示燈,如果沒有了它,程式也就無法運行了。
2:它屬於線程私人的:每個線程內部都有一個程式計數器,為了保證每個線程切換前後都能正常運行。
四:Java虛擬機器棧:
虛擬機器棧主要是作用在java方法運行時候,每個線程在運行到一個方法的入口都會建立一個棧幀,建立棧幀的目的在於儲存局部變數、運算元棧、動態連結、方法出口等資訊。按照這個原理,那麼一個方法在從調用直到完全執行完畢,都會對應一個從入棧到出棧的過程。
這裡我們說明一下局部變數,它的含義就是定義在java方法內部中的變數,對應於java中8大基礎資料型別 (Elementary Data Type),byte、int、float、boolean、char、short、long、reference:(long和double佔用2個空間,其餘1個)局部變數表裡我們需要注意的是,它在編譯期已經完全了記憶體配置,這樣棧幀在用局部變數的時候,它佔用的記憶體是已經確認的,不需要再分配,這就一定程度上減少了棧幀的工作量。
注意:
1:當線程請求的棧幀的深度大於虛擬機器允許的深度,就會拋出stackOverFlow異常
2:我們平時所說的堆棧,其中的棧就是指的是這裡的棧幀
3:它也是線程私人的,並且和線程的生命週期相同。
圖 2-1:虛擬機器棧
五:本地方法棧:
本地方法棧,顧名思義它代表的就是本地方法Native執行的棧幀,在jdk的源碼中我們可以看到很多命名為Native的方法,Native方法很大一部分是採用C/C++寫的,但是它對Native採用的資料結構、語言等都沒有做具體的要求,這一點完全是由java虛擬機器進行實現的。
注意:
1:這個地區會拋出oom或者sof(stackoverflowError)異常
2:同樣它屬於本地線程私人的
六:Java堆:
Java堆主要是儲存物件的地方,同時它也是java記憶體管理地區最大的一塊。當我們在程式中new一個對象出來或者建立一個數組,就會把Object Storage Service這個地區。它是java的最重要的公用地區之一,與線程私人相對,它是屬於線程公有的。基本上所有的對象都會儲存在此地區,但也不是絕對的(隨著JIT技術的成熟,這一點會發生微變)。堆是主要存放對象的地方,同時它就會產生另一個問題就是GC,當垃圾進行回收的時候,虛擬機器並沒有開闢新的空間,還在此地區進行,同時這個地區也叫“GC堆”,再細緻劃分下去,分為新生代、老年代,同時還分為Eden空間、From survivor空間、To Survior地區。為什麼要劃分的這麼細緻呢?其實還是主要還是為了更加有效率的回收,劃分的地區越細緻,那麼記憶體回收行程收集的時候只要去對應的地方直接回收就行,不用加上額外的判斷邏輯。
注意點:
1:堆在邏輯上是連續不間斷的記憶體空間,但是在物理上可以是不連續的記憶體空間
2:在堆中如果沒有完成記憶體的執行個體分配就會拋出oom
3:java堆是線程公有的,所有的線程共用這一片地區
圖 3-1 java堆記憶體
七:方法區
方法區主要是用來存放已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的代碼等資料,比如我們在代碼中定義的Constant常量就會在這個地區儲存。在java虛擬機器規範中,它是屬於堆的邏輯部分。同時在這個地區中,它也會有記憶體回收行程工作,這個地區叫做“永久代”,之所以叫做永久代,因為它比新生代和老年代擁有更長的生命週期,但是並不是在這個地區它就會萬事大吉了,永久代依然會存在記憶體回收的情況,只不過相對來說較少
注意點:
1:此地區屬於線程公有的,線程的類資訊、常量、靜態變數、編譯代碼都在此地區進行儲存
2:此地區會拋出oom異常,發生在方法區無法進行記憶體配置時
圖:4-1 方法區
八:運行時常量池:
運行時常量池主要是方法區的一部分,class檔案除了有類的版本、欄位、方法介面等描述資訊外,還有一項資訊是常量池,用於儲存編譯期產生的各種字面量和符號引用,這部分內容將在類載入後進入方法區運行時常量池中存放。
注意:此地區同樣會發生oom異常
九:總結
本篇博文主要是對java的記憶體資料模型地區劃分進行了簡單的介紹,沒有深入細緻過多的介紹。不過我們在整體上有一個感性的認識,理解java記憶體地區的劃分,以及這樣的劃分的好處,還有設計到線程的部分,理解並發的問題。這部分屬於虛擬機器底層的東西,屬於進階。相信看完這篇文章後,我希望能回答以下幾個問題。同時這些東西也是考究我們對於底層的認識,面試中也是經常會被提及到的東西
1:java記憶體模型分為哪幾個地區?
2:請說出屬於線程私人部分和線程公有部分的地區?
3:我們都知道記憶體是在按照“代”進行劃分的,那麼請問java堆中記憶體分為哪幾代?分別位於什麼地區?
4:為什麼要把java記憶體劃分這麼多,劃分這麼多不嫌麻煩嗎?有什麼好處
我希望能思考以上問題,學習的時候就要進行多思考,深入思考這是為什嗎?這篇博文的介紹就到此結束了,下一篇見,對於java記憶體模型的理解有助於我們處理並發的問題,這是屬於javaEE中高端進階必不可少的基礎,在此留個記錄,同時分享出來,讓大家也能清楚的認識Java記憶體模型同時提醒自己深入理解。
主要參考資料:
《深入Jvm虛擬機器》
Java記憶體資料模型