1、前言
Java程序的最大特點就是不用程序員對內存進行管理,它是由Java虛擬機自動管理的,那麼Java虛擬機中的內存是如何組織分佈的呢?
2、程序計數器
正在執行的虛擬機字節碼指令的地址。
3、Java虛擬機棧
Java方法執行的內存模型。
每個方法在執行的同時都會創建一個棧幀,用以存儲:
- 局部變量表,
- 操作數棧,
- 動態鏈接,
- 方法出口等信息。
每一個方法從調用直至執行完成的過程,都對應這個一個棧幀在虛擬機棧中入棧到出棧的過程。
局部變量表部分是棧中存儲的主要內容,其中存放了:
- 編譯器可知的基本數據類型
- 對象引用Reference
- returnAddress類型(指向了一條字節碼指令的地址)
局部變量表所需的內存空間在編譯期間就確定了,完成了分配,在方法運行期間不會改變局部變量表的大小。
4、本地方法棧
HotSpot將本地方法棧和虛擬機棧合二爲一。
5、Java堆
所有的對象實例及數組都在堆上分配。是垃圾收集管理的主要區域。
基本都採用分代回收的算法,新生代,老年代,以及永久代。
從內存分配的角度來看,線程共享的Java堆中可能劃分出多個線程私有的分配緩衝區TLAB,目的都是爲了更好的分配內存與回收內存。
6、方法區
用於存儲已被虛擬機加載的以下內容:
- 類信息
- 類的版本
- 字段
- 方法描述信息
- 接口描述信息
- 常量
- 靜態變量
- 即時編譯器編譯後的代碼等數據
JDK1.8之前將方法區放入永久代中,方便做內存管理,可是會容易遇到內存溢出的問題(因爲永久代中內存回收策略目標主要是針對常量池的回收和對類型的卸載,內存的回收策略很嚴格,導致內存很少被釋放)。
JDK1.8後,將方法區挪入了元空間Metaspace中,元空間內存隸屬於本地緩存,不受到Java虛擬機內設置的內存區域xmx的限制。
7、運行時常量池
運行時常量池,是方法區的一部分,用於存放編譯器生成:
- 字面量
- 符號引用
這部分內容將在類加載後,進入方法區的運行時常量池存放。除了保存Class文件中描述的符號引用外,還會把翻譯出來的直接引用也存儲在運行時常量池中。
運行期間也可能將新的常量放入池中,比如String類的intern( )方法。
8、本文總結
Java運行時內存區域,以上的描述可以總結爲下圖,下圖中也總結了對應的功能。
各內存區域可能發生的異常情況如下:
程序計數器 | Java虛擬機棧 | 本地方法棧 | Java堆 | 方法區 | 運行時常量池 | 直接內存 | |
---|---|---|---|---|---|---|---|
線程私有 | Y | Y | Y | ||||
是否會發生OutOfMemoryError的異常 | 不會 | Y,也可能發生StackOverflowError異常。 | Y | Y | Y | Y | Y |