你點贊了嗎?你關注了嗎?每天分享干貨好文。
高并發解決方案與架構設計。
海量數據存儲和性能優化。
通用框架/組件設計與封裝。
如何設計合適的技術架構?
如何成功轉型架構設計與技術管理?
在競爭激烈的大環境下,只有不斷提升核心競爭力才能立于不敗之地。
留言【我要晉級】,一對一指導,帶你晉級。
一、JVM 內存結構總覽
JVM 內存結構是 Java 程序運行的基石,定義了數據在虛擬機中的存儲與訪問規則。其核心分為??線程私有區域(線程隔離)??和??線程共享區域?,具體劃分如下圖
線程私有區域(線程隔離)? :程序計數器、虛擬機棧、本地方法棧
線程共享區域?:堆、方法區
從類型上分,也可以大致分為:堆、棧。堆是存儲單元,主要作用是存儲數據的。棧是運行單元,主要作用是解決程序如何運行。一個為線程執行服務,一個為全局數據存儲。
二、程序計數器(Program Counter Register)
程序計數器是線程私有的內存區域,用于存儲當前線程執行的字節碼指令地址。如果線程正在執行本地方法,程序計數器的值為空(Undefined)。
程序計數器是一塊較小的內存空間,也是運行速度最快的存儲區域。它可以看作是當前線程所執行的字節碼的行號指示器。在虛擬機的概念模型里(僅是概念模型,各種虛擬機可能會通過一些更高效的方式去實現),字節碼碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都需要依賴這個計數器來完成
說人話就是:用于存儲當前線程執行的字節碼指令地址。
由于 Java 虛擬機的多線程是通過線程輪流切換并分配處理器執行時間的方式來實現的,在任何一個確定的時刻,一個處理器(對于多核處理器來說就是一個內核)都只會執行一條線程中的指令。因此,為了線程切換后能恢復到正確的執行位置,每條線程都需要一個獨立的程序計數器,各條線程之間計數器互不影響,獨立存儲,我們稱這類的內存區域為“線程私有”的內存。
如果線程正在執行的是一個 Java 方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址;如果正在執行本地方法(Native),這個計數器值則為空(Undefined)。
此內存區域是唯一一個在Java虛擬機規范中沒有任何 OutOfMemoryError 情況的區域。
生命周期:程序計數器的生命周期與線程的生命周期一致。
三、虛擬機棧(VM Stack)
虛擬機棧是線程私有的內存區域,由多個棧幀(Stack Frame)構成,在當前線程中,每調用一個方法,都會有一個對應的棧幀進行入棧,用于存儲方法調用的局部變量、操作數棧、動態鏈接、方法出口等信息。當方法結束時,對應的方法的棧幀就會進行出棧。每個線程在創建時都會分配一個棧。
虛擬機棧包含以下幾個部分:
局部變量表:用于存儲方法參數和局部變量。
操作數棧:用于存儲方法執行過程中的操作數。
動態鏈接:指向運行時常量池中該方法的引用。
方法出口:記錄方法返回的地址。
一些附加信息
生命周期:棧的生命周期與線程的生命周期一致。線程啟動時創建棧,線程結束時銷毀棧。
設置大小:Java 虛擬機規范允許 虛擬機棧的大小是動態的或者是固定的。可以通過參數-Xss
來設置線程的最大棧空間,棧的大小直接決定了函數調用的最大可達深度。
當棧達到最大棧空間限制時,又要進行新的方法調用(入棧操作)時,就會拋出 StackOverflowError 異常。
當線程創建的時候,沒有足夠的空間穿件對應的虛擬機棧時,就會拋出 OutOfMemoryError 異常。
四、本地方法棧(Native Method Stack)
本地方法棧與棧類似,但它是為本地方法(Native Method)服務的。本地方法是用其他語言(如 C、C++)編寫的方法。
生命周期:本地方法棧的生命周期與線程的生命周期一致。
常見問題也與虛擬機棧類似。
五、堆(Heap)
堆是 JVM 中最大的一塊內存區域,是存儲 Java 程序中絕大多數的對象實例。是所有線程共享堆內存。Java 虛擬機規范中,把堆進行了劃分,主要分為:新生代、老年代、元空間(JDK 8 之前是永久代)。
生命周期:堆的生命周期與 JVM 的生命周期一致。JVM 啟動時創建堆,JVM 關閉時銷毀堆。
設置大小:堆的大小可以通過-Xmx
(最大堆)、-Xms
(初始堆)控制大小。
新生代(Young Generation)
新創建的對象一般首先分配在新生代中,除非對象過大直接進入老年代。新生代又分為 Eden 區和兩個 Survivor 區(通常稱為 S0 和 S1)。默認占比是 8 : 1 : 1。正常情況下兩個 Survivor 區總有一個是空的。后面聊垃圾回收的時候,我們可以細聊。
當 Eden 區滿了,會執行 ?Minor GC?,將 Eden 區和當前使用的 Survivor 區的幸存對象移動到空的 Survivor 區中。每經歷過一次 ?Minor GC?,幸存的對象其年齡計數器 +1,達到閾值(默認15)后會晉升至老年代。
老年代(Old Generation)
老年代通過是內存滿時執行垃圾回收,如果回收后依舊沒有空出空間,新對象無位置存儲,則會拋出 OutOfMemoryError 異常。
元空間(Metaspace)
存儲類信息、常量池等。
元空間可以通過-XX:MetaspaceSize
(初始大小)、-XX:MaxMetaspaceSize
(最大限制)?控制大小。
不論是 JDK 8 之前的永久代,還是 JDK 8 之后的元空間,其實都可以理解為虛擬機規范中方法區的具體實現。
雖然虛擬機規范把方法區描述為堆的一部分,但是它其實有一個別名 Non-Heap(非堆)。具體的我們詳見方法區部分。
六、方法區(Method Area)
方法區是虛擬機規范中定義的一個概念,是所有線程共享的內存區域,主要用于存儲類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。然后虛擬機規范中并沒有規定如何去實現它,不同的虛擬機廠商有不同的實現。
永久代(PermGen)則是 Hotspot 虛擬機特有的一個概念,在 JDK 8 中,又被改為元空間。
其實永久代合元空間都可以理解為是方法區的具體實現。
生命周期:方法區的生命周期與 JVM 的生命周期一致。當 JVM 啟動時,方法區被創建;當 JVM 關閉時,方法區被銷毀。
方法區主要包含以下幾個部分:
類信息:包括類的名稱、訪問修飾符、字段描述、方法描述等。
運行時常量池:用于存儲編譯期生成的各種字面量和符號引用。
靜態變量:類級別的靜態變量。
即時編譯器編譯后的代碼:JIT(Just-In-Time)編譯器將熱點代碼編譯為本地機器代碼后存儲在此。
設置大小:JDK 8 之后,可以使用參數 -XX:MetaspaceSize
和 -XX:MaxMetaspaceSize
指定。
方法區的大小決定了JVM 可以加載多少個 Class,如果加載的 Class 過多,則會拋出 OutOfMemoryError
異常。如果不指定大小的情況下,默認虛擬機會耗盡系統所有的可用內存。
-XX:MetaspaceSize
:設置初始的元空間大小。64 位 JVM默認的 -XX:MetaspaceSize=2075MB
,這是初始大小,當達到這個大小后,將會觸發 Full GC(Stop the world),卸載無用的 Class(Class 對應的ClassLoader 不再存活),并重置元空間大小(不超過 -XX:MaxMetaspaceSize
),重置的值取決于 Full GC 后釋放了多少的元空間。
如果初始化-XX:MetaspaceSize
過低,元空間的大小會發生多次調整,為了避免因為元空間初始大小過小導致的頻繁 Full GC,建議將 -XX:MetaspaceSize
設置為一個相對較高的值。
架構設計之道在于在不同的場景采用合適的架構設計,架構設計沒有完美,只有合適。
在代碼的路上,我們一起砥礪前行。用代碼改變世界!
如果有其它問題,歡迎評論區溝通。
感謝觀看,如果覺得對您有用,還請動動您那發財的手指頭,點贊、轉發、在看、收藏。
高并發解決方案與架構設計。
海量數據存儲和性能優化。
通用框架/組件設計與封裝。
如何設計合適的技術架構?
如何成功轉型架構設計與技術管理?
在競爭激烈的大環境下,只有不斷提升核心競爭力才能立于不敗之地。
留言【我要晉級】,一對一指導,帶你晉級。