說到記憶體管理,筆者這裡想先比較一下java與C、C++之間的區別: 在C、C++中,記憶體管理是由程式員負責的,也就是說程式員既要完成繁重的代碼編寫工作又要時常考慮到系統記憶體的維護 在java中,程式員無需考慮記憶體的控制和維護,而是交由JVM自動管理,這樣就不容易出現記憶體泄漏和溢出的問題。然而,一旦出 ...
說到記憶體管理,筆者這裡想先比較一下java與C、C++之間的區別:
- 在C、C++中,記憶體管理是由程式員負責的,也就是說程式員既要完成繁重的代碼編寫工作又要時常考慮到系統記憶體的維護
- 在java中,程式員無需考慮記憶體的控制和維護,而是交由JVM自動管理,這樣就不容易出現記憶體泄漏和溢出的問題。然而,一旦出現記憶體泄漏和溢出方面的問題,如果不瞭解JVM的記憶體管理機制就很難找到錯誤所在。
1.JVM運行時數據區
JVM在運行java程式的時候會將它所管理的記憶體劃分為若幹個不同的區域,這些區域不僅有自己的用途,還有創建和銷毀的時間。一般來說包含以下幾個運行時數據區:
其中的橙色區域是各個線程私有的,即每個線程都會有自己的一份,而綠色區域是各個線程共用的。
(詳細介紹參看:http://blog.csdn.net/seu_calvin/article/details/51404589)
2.java對象的創建
- 類載入檢查
當JVM掃描到new關鍵字時,首先會去檢查這個指令的參數是否能夠在常量池中定位到一個類的符號引用,並且檢查這個類的符號引用代表的類是否已被載入、解析和初始化過。如果沒有,就必須先執行相應的類載入過程。
- 記憶體分配
當類載入檢查通過後,JVM需要為新生對象分配記憶體,即是把一塊確定大小的記憶體從java堆中劃分出來。常用的劃分方法有兩種:指針碰撞(要求堆記憶體絕對規整)、空閑列表(堆記憶體並不規整)。
- 記憶體初始化
JVM需要將分配到的記憶體空間都初始化為零值(不包括對象頭),這就保證了對象的實例欄位在java代碼中可以不賦初始值就直接使用,也就是說程式能夠訪問到這些欄位的數據類型所對應的零值。
- 對象初始化
執行<init>方法,將對象按照程式員的意願進行初始化。
3.對象的訪問定位
對象創建好了,我們還希望能夠快速的訪問到這些對象,這就需要JVM棧上的reference(引用)數據來找到堆中的具體對象,而目前使用最多的訪問方式有“句柄方式”和“直接指針”兩種。
- 使用句柄方式訪問的話,就需要在堆中劃分一部分記憶體來作為句柄池,reference變數中存儲的就是對象的句柄地址,而句柄中包含了對象實例數據和類型數據各自的具體地址信息。
- 使用直接指針訪問的話,reference變數中存儲的直接就是對象地址,但是需要考慮如何放置類型數據的相關信息。
4.記憶體溢出異常
JVM運行時數據區除了PC寄存器之外,其他的記憶體區域都有可能發生記憶體溢出的異常情況。PC寄存器是唯一一個在JVM規範中沒有規定任何OutOfMemoryError(OOM)情況的區域。
- 堆溢出
java中的堆用於存儲對象實例,如果不斷地創建對象,並且保證GC Roots到對象之間有可達路徑以避免GC的回收處理,那麼在對象的數量達到最大堆的容量限制後就會發生堆溢出的異常情況。
- 棧溢出(包括JVM棧和本地方法棧)
1.如果線程請求的棧深度大於JVM所允許的最大深度,將拋出StackOverflowError異常;
2.如果JVM在擴展棧時無法申請到足夠的記憶體空間,將拋出OutOfMemoryError異常。
- 此外,還有方法區溢出、常量池溢出、本機記憶體溢出等等。