前言
java作為一款能夠自動管理內存的語言,與傳統的c/c++語言相比有着自己獨特的優勢。雖然我們無需去管理內存,但為了防范可能發生的異常,我們需要對java內部數據如何存儲有一定了解,已應對突發問題,寫出更好的程序
JVM對運行時程序內存的划分
java程序在被編譯成字節碼后,由JVM執行,執行期間產生的所有數據,會被分門別類的存儲在JVM預設好的區域里,具體情況如下所示
java6時方法區還屬於JVM管理的內存,那時俗稱為“永久代”,負責存儲:被虛擬機加載的類型信息、方法信息、常量(包括字符串常量)、靜態變量等等
java7時把永久代里的字符串常量池、靜態變量移動到了堆中
java8廢除永久代,改用元空間來實現方法區,原來java7中永久代的剩余內容移動到元空間中
以下為java8的內存分布圖
Tips:紅色是線程共享的,黃色是線程私有的
接下來我們着重討論Java8中的內存分布情況
JVM管理的內存
這部分內存在JVM中,由JVM直接分配,初始大小、最大大小都可以由JVM進行配置
程序計數器
是一段較小的內存空間,用於告訴字節碼解釋器下一條執行哪一個字節碼指令。是唯一一個在《java虛擬機規范》沒有規定任何OutOfMemoryError的區域
每條線程必須有獨立的程序計數器,以確保切換線程時,線程可以在正確的位置繼續執行字節碼
Tips:當執行Native方法時,計數器值為空(undefined)
虛擬機棧
我們平常俗稱的棧指的就是虛擬機棧,用來描述和存儲Java方法的內存模型。里面的數據生命周期在編譯時就已經確定了,比如局部變量方法調用結束就該釋放,內存很容易管理,所以並不是很依賴GC
具體行為:
每當執行一個方法時,JVM就會創建一個棧幀放進虛擬機棧中
棧幀的內容包括但不限於:
- 局部變量表(也包括形參)
- 八大基本數據類型
- 引用類型(直接指針或者句柄,由具體的JVM實現決定)
- returnAddress類型 (用於方法結束回到原來的字節碼位置繼續執行)
- 操作數棧
- 開始時是空的,運行后逐漸入棧出棧,比如算數運算就是操作數棧進行的
- 動態連接
- 方法出口
直到方法執行結束,JVM就會將此方法的棧幀出棧
顯然,如果多個線程共用同一虛擬機棧,會出現某個線程的方法還沒執行完畢,又被另一線程的棧幀入棧,破壞了方法數據結構。所以虛擬機棧是線程私有的
returnAddress作用
用於執行完方法后回到調用方法的位置繼續往下執行
當一個棧幀入棧時,returnAddress保存當前程序計數器的值,即當前字節碼位置,然后開始執行方法,方法執行結束后,用returnAddress的值恢復程序計數器,即回到調用方法時的字節碼位置
本地方法棧
幾乎與虛擬機棧一樣的作用,其區別是,本地方法棧為本地Native方法服務,通常是本地的C/C++庫的方法,而虛擬機棧是為java方法服務的
堆區
通常是JVM中最大的內存區域,也是垃圾收集器GC最經常光顧的區域,里面的數據生命周期無法在編譯時確定,需要GC來幫助判斷是否是“死亡變量”,以回收沒必要的內存。
存儲的內容:
- 對象的實例
- 數組
- 字符串常量池
- 物理上在堆區,邏輯上是方法區的內容
- 靜態變量
- 物理上在堆區,邏輯上是方法區的內容
本地內存
默認情況使用大小只受限於本地內存的實際大小
但我們任可以通過JVM配置限制使用大小
這里面的數據一般不經常變動,存放在這里被JVM間接管理較為合適(間接管理速度肯定比JVM內部的慢些)
方法區(元空間)
java8使用元空間來實現的方法區,《Java虛擬機規范》中方法區為堆區的邏輯部分,堆中的對象依靠方法區存儲的類信息來生成實例
存儲的內容:
- 運行時常量池
- 字面量
- 符號引用
- 類信息
- 類型
- 完整名
- 修飾符
- 父類、接口信息
- 域
- 名稱
- 類型
- 方法
- 名稱
- 參數
- 返回值
- 字節碼
- 類型
以上包含了一些有代表性的內容,並不代表方法區存儲的全部內容
直接內存
此部分並不常用,至少對我目前來說。
在jdk1.4中加入了NIO(New Input/Putput)類,引入了一種基於通道(channel)與緩沖區(buffer)的新IO方式,它可以使用native函數直接分配堆外內存,然后通過存儲在java堆中的DirectByteBuffer對象作為這塊內存的引用進行操作,這樣可以在一些場景下大大提高IO性能,避免了在java堆和native堆來回復制數據。--《深入理解java虛擬機》