Java虛擬機《深入理解 JVM 》之JVM 內存結構

Java 虛擬機在運行 Java 程序 時,把它所管理的內存劃分為若干個不同的數據區域,主要包括以下五個部分:程序計數器、Java 堆、Java 虛擬機棧、方法區和本地方法棧。

Java虛擬機《深入理解 JVM 》之JVM 內存結構

JVM 內存結構

程序計數器

程序計數器是當前線程所執行的字節碼的行號指示器,它會指出下一條將要執行的指令的地址,字節碼解釋器就是通過改變計數器的值來選取程序接下來執行的操作。

程序計數器是線程私有的一小塊內存,每條線程都要有一個獨立的程序計數器,以使線程切換後恢復到正確的執行位置。

  • 如果線程正在執行 Java 方法,則計數器記錄的是正在執行的虛擬機字節碼指令的地址
  • 如果執行 native 方法,則計數器為空

它也是唯一一個不會出現 OutOfMemoryError 的內存區域。

Java 虛擬機棧

與程序計數器一樣,Java 虛擬機棧也是線程私有的,在線程創建時 Java 棧會被創建,每個方法在在執行的同時都會創建一個棧幀,用於存放局部變量表,操作數棧,動態鏈接,方法出口等信息。每一個方法從調用直至執行完成,都對應著一個棧幀在虛擬機棧中入棧到出棧的過程。

一般所謂的“棧”,指的是虛擬機棧中局部變量表部分,其中存放了各種基本數據類型( 8 種),對象引用(reference 類型) 和 returnAddress 類型。局部變量表所需的空間在編譯期就已經確定並完成分配,在方法運行期間不會被改變。

Java 虛擬棧中可能出現兩種異常:

  • StackOverflowError:線程請求的棧深度大於虛擬機所允許的深度
  • OutOfMemoryError:虛擬機棧擴展時無法申請到足夠的內存

本地方法棧

本地方法棧與 Java 虛擬機棧的作用類似,區別是 Java 虛擬機棧為虛擬機執行 Java 方法服務,而本地方法棧為虛擬機執行 Native 方法服務。有的虛擬機(例如 HotSpot 虛擬機)直接把本地方法棧和 Java 虛擬機棧合併在一起。

本地方法棧也可能會拋出 StackOverflowError 和 OutOfMemoryError 異常。

Java 堆

Java 堆是是虛擬機中最主要的內存區域。它為線程共享,在虛擬機啟動時創建,幾乎所有的對象實例都存儲在 Java 堆中。

Java 堆也被稱作 "GC" 堆。從內存回收角度看,可分為新生代和老年代。而新生代又可分為 Eden 區、From Survivor 區、To Survivor 區等。

Java 堆的實現,既可以實現為固定的,也可以是擴展的。當前虛擬機都按照可擴展來實現,通過 -Xmx 和 -Xms 控制堆大小。

如果堆中沒有內存並且也無法再擴展時,會拋出 OutOfMemeoryError 異常。

方法區

方法區與 Java 堆一樣,為線程共享。用於存儲已被虛擬機加載的類信息,常量,靜態變量,即時編譯器編譯後的代碼等數據。也叫作 Non-Heap(非堆)。

如果方法區無法滿足內存分配需求,會拋出 OutOfMemoryError 異常。

運行時常量池

運行時常量池是方法區的一部分。Class 文件中的常量池用於編譯期生成的各種字面量和符號引用,這部分內容在類加載後被存入運行時常量池。

動態性是運行時常量池相對於 Class 文件常量池的一個重要特徵,即不要求常量一定只有編譯期才能產生,運行期間也可能將新的常量放入池中。

運行時常量池受到方法區內存的限制,如果常量池無法再申請內存,就會拋出 OutOfMemoryError 異常。

直接內存

直接內存並不由 JVM 管理,它是利用 Native 函數庫在 Java 堆外申請分配的內存區域,可以避免在 Java 堆和 Native 堆中複製數據以提高性能。

例如 NIO 中的 DirectByteBuffer 就可以作為這塊內存的引用進行操作直接內存。

永久代與元空間

有時會看到方法區被稱為永久代,其實兩者有著本質的區別。方法區是 JVM 規範中的定義,而永久代是 JVM 規範的一種實現,並且只有在 HotSpot 虛擬機中如此,其他虛擬機中沒有永久代的說法。

在 JDK1.6 之前,HotSpot 虛擬機把 GC 分代收集擴展至方法區,或者說使用永久代實現方法區。不過永久代有 -XX:MaxPermSize 的上限,很容易遇到內存溢出問題。

所以在 JDK1.7 中,將部分數據已經轉移 Java Heap 或 Native Heap 中,例如:將原本放在永久代中的字符串池和類的靜態變量移出到 Java Heap 中,將符號引用轉移到 Native Heap 中。但永久代仍然存在,並沒有移除。

在 JDK1.8 中,取消了永久代,代替為元空間實現,它也是 JVM 規範中方法區的一種實現。不過它與永久代最大的不同是:元空間並不在虛擬機中,而是將元空間放到本地內存中。所以默認情況下,它只受本地內存的限制,可以通過 -XX:MetaspaceSize 參數設置初始空間大小,默認沒有最大空間限制。

常見的 OOM 及原因

Java 中的 OOM 指的就是 java.lang.OutOfMemoryError 異常。主要有以下幾種:

java.lang.OutOfMemoryError:Java heap space

Java 堆中主要用於存放各種對象實例。當堆中沒有足夠的空間分配給新對象時,或者說達到了堆空間設置的最大空間限制,則會拋出此異常。

引起內存溢出的原因主要有:

  • 流量訪問量大,超過設置的堆空間大小;
  • 內存洩露,不能被回收的對象消耗過多堆空間;

java.lang.OutOfMemoryError:Permgen space

在 JDK7 中,HotSpot 虛擬機使用永久代實現方法區,永久代較小,而且回收效率較低,很容易出現內存溢出。

因此,JDK8 取消了永久代,使用元空間來實現方法區,存放在本地內存中。

java.lang.OutOfMemoryError:Metaspace

方法區主要存儲類的元信息,HotSpot 元數據區。當元空間沒有足夠的空間分配給加載的類時,會拋出此異常。

引起元數據區空間不足的原因主要有:

  • 加載的類太多,常見於 jsp 頁面過多時;
  • 元空間被實現在堆外,主要受到進程本身的內存限制,一般很難出現溢出。

關注我:私信回覆“555”獲取往期Java高級架構資料、源碼、筆記、視頻Dubbo、Redis、Netty、zookeeper、Spring cloud、分佈式、高併發等架構技術往期架構視頻


分享到:


相關文章: