1. 程式人生 > >JVM內存模型詳解

JVM內存模型詳解

基本 過程 nio 認識 靜態變量 maxperm 工作原理 函數 不變

JVM內存模型也叫JVM運行時區域,是認識和了解JVM工作原理的基礎,從java誕生以來,JVM內存模型基本保持著大同小異的整體形態,由此可見JVM內存模型是相當穩定的,直到jdk1.8之後JVM內存模型中才將permGen(永生代),也就是過去的方法區完全去除,使用metaspace取而代之,但是從整個JVM內存形態來說其實並沒有產生太大的變化,有點“換湯不換藥”的味道。除此之外JVM內存模型的設計原理也充分考慮了java程序的運行過程以及GC的策略,所以JVM內存模型是一個既基礎又復雜的內存結構。

jdk1.8以前的內存模型:

技術分享

jdk1.8內存模型:

技術分享

兩種內存結構其實大同小異,這種差異在最後進行說明,圖中深綠色的部分區域代表是線程私有的,而淺黃色部分代表的是所有線程共享的區域。首先來分別對不同的內存部分進行說明。

一、程序計數器

程序計數器是JVM執行字節碼指令時的一個計數器指針,在對java代碼進行執行的時候(如分支、循環、方法跳出、線程切換)都會使用到這個計數器,這個計數器的內存空間是非常小的,並且為線程私有,java多線程程序是通過切換線程占用CPU時間片的方式來執行的,為了使得每次線程切換之後對應的線程都能夠指向正確的代碼位置(字節碼指令),這個時候就會使用到程序計數器,每一個線程都有自己對應的程序計數器,所以程序計數器是一個線程私有的內存區域。對於java方法程序計數器會指向對應的字節碼指令,而對應native方法則計數器為空。最後,這塊區域是在JVM內存模型中唯一一塊沒有OOM異常的區域。

二、虛擬機棧

虛擬機棧也被稱為java虛擬機棧,這塊區域同樣是線程私有的,也就是生命周期與線程相同。虛擬機棧描述的是java方法執行時的內存模型,每個java方法在執行的時候都會創建一個方法棧幀用於存儲局部變量表,操作數棧,動態的鏈接,方法返回值等。可以這麽理解,一個java方法從開始執行到結束,實質上就是對應的一個方法棧幀從壓棧到出棧的過程。

虛擬機棧中的局部變量表的空間大小是在編譯的時期就已經確定了,也就是說在java方法的執行過程當中局部變量表的大小是確定不變的。在這個區域中如果線程所請求的棧的深度大於了虛擬機所允許的深度,就會拋出StackOverflowError(自己可以通過實現一個“跳不出來的”遞歸方法來模擬這種情況)。另外虛擬機棧的空間大小是可以動態擴展,如果是一個固定空間大小的虛擬棧(設置-Xss1m),java程序在創建線程時申請不到足夠的空間會發生OOM。

三、本地方法棧

本地方法棧也是線程私有的並且與虛擬機棧的作用是完全一樣的,不同點是本地方法棧是對native方法執行過程內存模型進行管理,會記錄native方法在執行過程的相關數據(局部變量表等),完成對native方法棧的壓棧和出棧過程。同樣會發生StackOverflowError和OutOfMemoryError。

四、堆區

java 堆(heap)是JVM內存模型當中最大的一塊區域, 並且是所有線程共享的一塊區域,此區域主要用於存放java對象的實例,基本上所有的對象實例都會在這裏分配內存(java.lang.Class對象不會,會在類加載中直接分配到方法區中)。堆是垃圾回收器主要“關照”的區域,由於有垃圾回收算法的策略影響,所以一整塊大的堆實際也可以往下進行細分,新生代(Eden區,from suvivor區,to survivor區)、老年代。java的堆在物理空間上是可以允許不連續的,只要在邏輯上連續即可。可以通過-Xmx和-Xms來調整堆的大小,如果分配的對象實例已經超過了堆分配大小的剩余空間,這時候會產生OutOfMemoryError。

五、方法區

很多人把方法區叫做永生代,因為這一個區域主要是存放類加載過程中字節碼文件的二進制字節流轉變後的內存結構以及一些類的變量或靜態變量,常量,這些數據模型都比較靜態,看起來不會被垃圾回收,但是實際上並不是這樣,JVM在後期的版本中實際上會對方法去中存儲的數據進行垃圾回收,只是回收的效果不太明顯(主要是針對於類的卸載以及常量池的回收)。在1.8以前可以設置-XX:MaxPermSize和-XX:PermSize來調整方法區的大小,如果分配的內存大小已經大於了方法區的剩余大小了,則會拋出OutOfMemoryError。可以通過一個死循環用cglib生成字節流進行加載的過程來模擬異常。

六、常量池

常量池主要存儲編譯時期的字面量和符號引用(類、接口、方法、變量的符號),它是方法區的一部分,因為是方法區的一部分,所以也會出現OutOfMemoryError。很多人可能認為常量池的數據是在程序編譯的時期就已經確定好了,但是實際上在java運行過程當中也可以動態添加常量到池中(String的intern方法)

七、直接內存區

直接內存區域(Direct Memory)並不是JVM內存模型的一部分,而是通過native本地函數在操作系統中分配的一塊內存區域,而在JVM中通過DirectByteBuffer對象對其進行引用操作(在JDK1.4引入NIO後有buffer和Channel出現),同樣這一塊內存區域因為會受到操作系統內存大小的限制,所以也會出現OutOfMemroyError。

七、MetaSpace

jdk1.8之後引入了一個新的區域叫做metaSpace,可以通過-XX:MaxMetaSpaceSize=256進行設置。在1.8之後已經廢棄了之前的PermGen區域,所以無法通過設置PermSize和MaxPermSize來對其方法區的內存大小進行設置。MetaSpace實際上代替了PermGen區域用於存儲類加載過程中的二進制字節流內存結構,而以前的常量池則被轉移到了java堆中。這一塊區域同樣會出現OutOfMemoryError。

JVM內存模型詳解