JAVA-大白話探索JVM-執行時記憶體(三)

Berton Lee發表於2019-01-29

前面章節

JAVA-大白話探索JVM-類載入器(一)

JAVA-大白話探索JVM-類載入過程(二)

JAVA-大白話探索JVM-執行時記憶體(三)

JVM執行時記憶體

通過之前的章節,我們知道.class類如何載入到記憶體中,如圖紅框

image.png
JAVA-大白話探索JVM-執行時記憶體(三)

開始講講記憶體空間

先了解JVM的週期

  1. JVM在java程式執行時執行,結束時停止。
  2. 一個java程式對應開啟一個JVM程式
  3. JVM的執行緒分為兩種:守護執行緒和普通執行緒
    • 守護執行緒屬於JVM自己使用的執行緒,如GC
    • 普通執行緒是java程式的執行緒

JAVA-大白話探索JVM-執行時記憶體(三)

執行緒私有資料區

  • Java棧(VM Stack)
  • 本地方法棧(NM Stack)
  • 程式計數器及隱含暫存器(Program Counter Register)

執行緒共享資料區

  • 方法區(Method Area)
  • Java堆(Heap)
  • 執行引擎
  • 本地方法介面
  • 本地方法庫

你會發現,這都是些什麼?????。。。。。。呃

不著急,一步一步來

首先,就是你了,方法區(Method Area,執行緒共享)

  1. 類的結構資訊和類靜態變數都儲存在方法區
    • 舉個例,例如執行時常量池,成員變數和方法資料,建構函式和普通函式的位元組碼內容,還包括一些在類、例項、介面初始化時用到的特殊方法。開發人員在程式中通過Class物件中的getName、isInstance等方法獲取資訊時,這些資料都來自方法區。
  2. 程式中的所有執行緒共享一個方法區,簡稱全域性共享
  3. 對於HotSpot虛擬機器,方法區對應為永久代(Permanent Generation),但本質上,兩者並不等價,僅僅是因為HotSpot虛擬機器的設計團隊是用永久代來實現方法區而已,對於其他的虛擬機器(JRockit、J9)來說,是不存在永久代這一概念的。
    • 使用永久代來實現方法區並不是一個好注意,由於方法區會存放Class的相關資訊,如類名、訪問修飾符、常量池、欄位描述、方法描述等,在某些場景下非常容易出現永久代記憶體溢位。如Spring、Hibernate等框架在對類進行增強時,都會使用到CGLib這類位元組碼技術,增強的類越多,就需要越大的方法區來保證動態生成的Class可以載入入記憶體。在JSP頁面較多的情況下,也會出現同樣的問題。
  4. 在JDK1.8下並沒有出現我們期望的永久代記憶體溢位錯誤,而是Metaspace記憶體溢位錯誤。這是因為Java團隊從JDK1.7開始就逐漸移除了永久代,到JDK1.8時,永久代已經被Metaspace取代,因此在JDK1.8並沒有出現我們期望的永久代記憶體溢位錯誤。
    • 在JDK1.8中,JVM引數-XX:PermSize和-XX:MaxPermSize已經失效,取而代之的是-XX:MetaspaceSize和XX:MaxMetaspaceSize。注意:Metaspace已經不再使用堆空間,轉而使用Native Memory
  5. 還有一點需要說明的是,在JDK1.6中,方法區雖然被稱為永久代,但並不意味著這些物件真的能夠永久存在了,JVM的記憶體回收機制,仍然會對這一塊區域進行掃描,即使回收這部分記憶體的條件相當苛刻。

呃。。。。。。。有點多,慢慢吸收,這方法區也需要好好琢磨琢磨,一不小心溢位就麻煩了。

其次,Java堆(Heap,執行緒共享)

  1. Java堆是JVM所管理的最大一塊記憶體,所有執行緒共享這塊記憶體區域,幾乎所有的物件例項都在這裡分配記憶體,因此,它也是垃圾收集器管理的主要區域。
  2. 從記憶體回收的角度來看,由於現在的收集器基本都採用分代收集演算法,所以Java堆又可以細分成:新生代和老年代,新生代裡面有分為:Eden空間、From Survivor空間、To Survivor空間。
  3. 有一點需要注意:Java堆空間只是在邏輯上是連續的,在物理上並不一定是連續的記憶體空間。
  4. 預設情況下,新生代中Eden空間與Survivor空間的比例是8:1,可以使用引數-XX:SurvivorRatio對其進行配置。大多數情況下,新生物件在新生代Eden區中分配,當Eden區沒有足夠的空間進行分配時,則觸發一次Minor GC,將物件Copy到Survivor區,如果Survivor區沒有足夠的空間來容納,則會通過分配擔保機制提前轉移到老年代去。
  5. 何為分配擔保機制?在傳送Minor GC前,JVM會檢查老年代最大可用的連續空間是否大於新生代所有物件的總空間,如果是,那麼可以確保Minor GC是安全的,如果不是,那麼會繼續檢查老年代最大可用的連續空間是否大於歷次晉升到老年代物件的平均大小,如果小於,直接進行Full GC,如果大於,將嘗試著進行一次Minor GC,Minor GC失敗才會觸發Full GC。注:不同版本的JDK,流程略有不同。
  6. Survivor區作為Eden區和老年代的緩衝區域,常規情況下,在Survivor區的物件經過若干次垃圾回收仍然存活的話,才會被轉移到老年代。JVM通過這種方式,將大部分命短的物件放在一起,將少數命長的物件放在一起,分別採取不同的回收策略。

Java棧(Stack,執行緒私有)、本地方法棧

Java棧

  1. java棧中只儲存基礎資料型別(四類八種)和自定義物件引用
  2. 存取型別:先進後出
  3. 棧內資料在超出其作用域將自動釋放
  4. 每個棧是執行緒私有,它們的生命週期與執行緒相同。
  5. 每個執行緒建立一個操作棧,每個棧又包含若干個棧幀,每個棧幀對應每個方法呼叫
  6. 棧幀:
    • 區域性變數區(方法內基本型別變數、變數物件指標)
    • 運算元棧區(存放方法執行過程中產生的結果)
    • 執行環境區(動態連結、方法返回相關資訊、異常捕捉)

本地方法棧

  1. 與JAVA棧類似
  2. 本地方法棧是在程式呼叫或JVM呼叫本地方法介面(Native)時候啟用
  3. 本地方法非java語言編寫,不受JVM管理
  4. HotSpot VM將本地方法棧和JVM棧合併了。

程式計數器(執行緒私有)

概念:在JVM概念模型裡,位元組碼直譯器工作時就說通過改變這個計算器的值來選取下一條需要執行的位元組碼指令。分支、迴圈、跳轉、異常處理、執行緒恢復等基礎功能都需要依賴這個計數器來完成。

  1. Java虛擬機器可以支援多條執行緒同時執行,多執行緒是通過執行緒輪流切換來獲得CPU執行時間的,每條執行緒都會有獨立的程式計數器
  2. 如果執行java方法,程式計數器記錄JVM位元組碼指令的地址,如果執行 native,計數器為空(Underfined)
  3. 程式計數器這個記憶體區域在JVM規範中是唯一沒有規定任何OutOfMemoryError的區域

執行時常量池(Runtime Constant Pool)

  1. 方法區的一部分,用於存放編譯期間生成的各種字面量(int,short等等)和符號引用(物件符號引用Integer,String)
  2. 除了編譯產生能存入,執行期間也能將新的常量放入池中(String.intern())
  3. 節省記憶體空間:常量池中如果有對應的字串,那麼則返回該物件的引用,從而不必再次建立一個新物件。
  4. 節省執行時間:比較字串時,==比equals()快。對於兩個引用變數,==判斷引用是否相等,也就可以判斷實際值是否相等
  5. Byte、Short、Integer、Long、Character這5種包裝類都預設建立了數值[-128 , 127]的快取資料。當對這5個型別的資料不在這個區間內的時候,將會去建立新的物件,並且不會將這些新的物件放入常量池中。
  6. Oracle對Java 7中的常量池做了一個非常重要的改變 — 常量池被重新定位到堆中。這意味著你不再受限於單獨的固定大小記憶體區域。所有字串現在都位於堆中,與大多數其他普通物件一樣,這使你可以在調整應用程式時僅管理堆大小。

JAVA-大白話探索JVM-執行時記憶體(三)

完了······

個人部落格文章連結 : www.ccode.live/bertonlee/l…

先暫時這麼多吧,以上是我個人針對JVM的總結,也方便大家快速理解跟鞏固,有錯誤的地方望告知下,謝謝。

歡迎關注

歡迎關注公眾號“碼上開發”,每天分享最新技術資訊

JAVA-大白話探索JVM-執行時記憶體(三)

相關文章