【圖文詳解系列】JVM 內存模型

內存模型

(1)線程私有區:

  • 進程計數器,記錄正在執行的虛擬機字節碼的地址;
  • 虛擬機棧:方法執行的內存區,每個方法執行時會在虛擬機棧中創建棧幀;
  • 本地方法棧:虛擬機的Native方法執行的內存區;

(2)線程共享區:

  • Java堆:對象分配內存的區域;
  • 方法區:存放類信息、常量、靜態變量、編譯器編譯後的代碼等數據;
    • 常量池:存放編譯器生成的各種字面量和符號引用,是方法區的一部分。

      詳細模型

進程計數器PC

進程計數器PC,當前線程所執行的字節碼行號指示器。每個線程都有自己計數器,是私有內存空間,該區域是整個內存中較小的一塊。

當線程正在執行一個Java方法時,PC計數器記錄的是正在執行的虛擬機字節碼的地址;當線程正在執行的一個Native方法時,PC計數器則爲空(Undefined)。

虛擬機棧

虛擬機棧,生命週期與線程相同,是Java方法執行的內存模型。每個方法(不包含native方法)執行的同時都會創建一個棧幀結構,方法執行過程,對應着虛擬機棧的入棧到出棧的過程。

棧幀(Stack Frame)結構

棧幀是用於支持虛擬機進行方法執行的數據結構,是屬性運行時數據區的虛擬機站的棧元素。見上圖, 棧幀包括:

  1. 局部變量表 (locals大小,編譯期確定),一組變量存儲空間, 容量以slot爲最小單位。
  2. 操作棧(stack大小,編譯期確定),操作棧元素的數據類型必須與字節碼命令序列嚴格匹配
  3. 動態連接, 指向運行時常量池中該棧幀所屬方法的引用,爲了 動態連接使用。
    • 前面的解析過程其實是靜態解析;
    • 對於運行期轉化爲直接引用,稱爲動態解析。
  4. 方法返回地址
    • 正常退出,執行引擎遇到方法返回的字節碼,將返回值傳遞給調用者
    • 異常退出,遇到Exception,並且方法未捕捉異常,那麼不會有任何返回值。
  5. 額外附加信息,虛擬機規範沒有明確規定,由具體虛擬機實現。

Java堆

Java堆,是Java虛擬機管理的最大的一塊內存,也是GC的主戰場,裏面存放的是幾乎所有的對象實例和數組數據。JIT編譯器有棧上分配、標量替換等優化技術的實現導致部分對象實例數據不存在Java堆,而是棧內存。

  • 從內存回收角度,Java堆被分爲新生代和老年代;這樣劃分的好處是爲了更快的回收內存;
  • 從內存分配角度,Java堆可以劃分出線程私有的分配緩衝區(Thread Local Allocation Buffer,TLAB);這樣劃分的好處是爲了更快的分配內存;

對象創建的過程是在堆上分配着實例對象,那麼對象實例的具體結構如下:

對於填充數據不是一定存在的,僅僅是爲了字節對齊。HotSpot VM的自動內存管理要求對象起始地址必須是8字節的整數倍。對象頭本身是8的倍數,當對象的實例數據不是8的倍數,便需要填充數據來保證8字節的對齊。該功能類似於高速緩存行的對齊。

另外,關於在堆上內存分配是併發進行的,虛擬機採用CAS加失敗重試保證原子操作,或者是採用每個線程預先分配TLAB內存.

對象分配規則

  • 對象優先分配在Eden區,如果Eden區沒有足夠的空間時,虛擬機執行一次Minor GC。
  • 大對象直接進入老年代(大對象是指需要大量連續內存空間的對象)。這樣做的目的是避免在Eden區和兩個Survivor區之間發生大量的內存拷貝(新生代採用複製算法收集內存)。
  • 長期存活的對象進入老年代。虛擬機爲每個對象定義了一個年齡計數器,如果對象經過了1次Minor GC那麼對象會進入Survivor區,之後每經過一次Minor GC那麼對象的年齡加1,直到達到閥值對象進入老年區。
  • 動態判斷對象的年齡。如果Survivor區中相同年齡的所有對象大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的對象可以直接進入老年代。
  • 空間分配擔保。每次進行Minor GC時,JVM會計算Survivor區移至老年區的對象的平均大小,如果這個值大於老年區的剩餘值大小則進行一次Full GC,如果小於檢查HandlePromotionFailure設置,如果true則只進行Monitor GC,如果false則進行Full GC。

參考資料

  1. http://gityuan.com/2016/01/09/java-memory/
  2. http://blog.csdn.net/universe_ant/article/details/58585854
  3. Java —— 運行時棧幀結構
  4. https://www.dazhuanlan.com/2019/12/07/5deaf86336d1d/

Kotlin開發者社區

專注分享 Java、 Kotlin、Spring/Spring Boot、MySQL、redis、neo4j、NoSQL、Android、JavaScript、React、Node、函數式編程、編程思想、"高可用,高性能,高實時"大型分佈式系統架構設計主題。

High availability, high performance, high real-time large-scale distributed system architecture design

分佈式框架:Zookeeper、分佈式中間件框架等
分佈式存儲:GridFS、FastDFS、TFS、MemCache、redis等
分佈式數據庫:Cobar、tddl、Amoeba、Mycat
雲計算、大數據、AI算法
虛擬化、雲原生技術
分佈式計算框架:MapReduce、Hadoop、Storm、Flink等
分佈式通信機制:Dubbo、RPC調用、共享遠程數據、消息隊列等
消息隊列MQ:Kafka、MetaQ,RocketMQ
怎樣打造高可用系統:基於硬件、軟件中間件、系統架構等一些典型方案的實現:HAProxy、基於Corosync+Pacemaker的高可用集羣套件中間件系統
Mycat架構分佈式演進
大數據Join背後的難題:數據、網絡、內存和計算能力的矛盾和調和
Java分佈式系統中的高性能難題:AIO,NIO,Netty還是自己開發框架?
高性能事件派發機制:線程池模型、Disruptor模型等等。。。

合抱之木,生於毫末;九層之臺,起於壘土;千里之行,始於足下。不積跬步,無以至千里;不積小流,無以成江河。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章