網絡上有很多關於JVM內存模型的文章,本文只針對JDK8的JVM內存模型予以小結,若有不對之處望留言指正。
其中,綠色表示線程私有空間,其他表示線程可共享空間
程序計數器 (無GC)
線程私有的一塊較小的內存空間,當前線程所執行的字節碼的行號指示器,JVM的多線程就是通過線程輪流切換並分配CPU時間分片的方式來實現的,在任意指定的時刻,一個處理器(一個內核)只會執行一個線程中的指令,為了線程切換后能恢復到正確的執行位置,每個線程都需要有一個獨立的程序計數器,各個線程之間互不影響,獨立存儲。
這個區域也是唯一一個在JVM規范中沒有規定任何OutOfMemoryError情況的區域。
java虛擬機棧 (無GC)
生命周期與線程相同,是java方法執行的內存模型,每個方法在執行的同時會在虛擬機棧中創建一個棧幀,用於存儲局部變量表、操作數棧、動態鏈接、返回值地址等信息。每個方法從調用直至執行完成的過程就對應着一個棧幀在虛擬機棧中入棧到出棧的過程。出棧相當於清空了數據,入棧出棧的時機很明確,所以這塊區域內存也是不需要GC。
虛擬機棧有2種異常:
1、StackOverflowError: 線程請求的棧深度大於虛擬機所允許的深度,特別是在有遞歸調用時
2、OutOfMemoryError: 虛擬機棧無法滿足線程所申請的空間需求,即使經過動態擴展任然無法滿足,將會導致該類異常的發送
本地方法棧 (無GC)
與虛擬機棧類型,主要區別在於虛擬機棧為虛擬機執行java方法服務,而本地方法棧是為運行本地native方法而服務,這塊區域也是不需要進行GC的,所謂本地方法就是一個java調用非java代碼的接口,該方法非java語言實現,大多數由C語言實現,Java通過JNI的方式來調用本地方法,而本地方法是以庫文件的方式存放的(Unix機器上是以SO文件形式)。通過調用本地庫文件的內部方法,使java可以實現和本機器的緊密聯系,調用系統級的各接口方法,當調用java方法時虛擬機會創建一個棧幀並壓入java棧,而當調用本地方法時,JVM會保持java棧不變,不會在java棧中壓入新的棧,只是簡單的動態鏈接並直接調用本地方法。
本地方法棧也有2種異常:
1、StackOverflowError: 線程請求的棧深度大於本地方法棧所允許的深度時
2、OutOfMemoryError: 本地方法棧空間需求不足,即使經過動態擴展任然無法滿足時
堆 (GC發生的區域)
堆是JVM內存占用最大,管理最復雜的一個區域,堆中主要存放對象的實例和數組,也是GC主要發生的區域。
堆空間大小不滿足時將拋出OutOfMemoryError異常。
本地內存 (無GC)
本地內存,也就是通常所說的堆外內存,包含元數據區和直接內存,也稱為C-Heap,是供JVM自身進程使用的,當java堆空間不足時會觸發GC,但本身不會觸發GC
元數據區
JVM規范中的方法區用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼數據。在JDK8中,元數據區是對方法區的一個實現,但不在虛擬機中,而是使用本地內存,默認情況下,可用於元數據區的本地變量是沒有限制的,但可以用MaxMetaspaceSize參數來設置上限值
元數據區會拋出OutOfMemoryError異常
直接內存
在JDK1.4中引入的NIO模型,一種基於channel與buffer的IO方式,可以使用Native庫函數直接分配堆外內存,然后通過一個存儲在java堆中的DirectByteBuffer對象作為這塊內存的引用進行操作,能在一些場景中顯著提高性能,因為避免了而在java堆中和Native堆中來回復制數據,在操作系統IO過程中,需要把數據從用戶態copy到內核態,然后再輸出到IO設備,所以從java堆內存輸出到IO設備需要經過2次IO,而DirectMemory在native堆上只需要一次copy
直接內存同樣會拋出OutOfMemoryError異常