前言
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虛擬機》
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/354427.html
標籤:Java
