JVM記憶體區域

語言: CN / TW / HK
深入理解執行時資料區
 
1. JVM 向作業系統申請記憶體:
JVM 第一步就是通過配置引數或者預設配置引數向作業系統申請記憶體空間,根據記憶體大小找到具體的記憶體分配表,然後把記憶體段的起始地址和終止地
址分配給 JVM ,接下來 JVM 就進行內部分配。
2. JVM 獲得記憶體空間後,會根據配置引數分配堆、棧以及方法區的記憶體大小 -Xms30m -Xmx30m -Xss1m -XX:MaxMetaspaceSize=30m
3. 類載入(類載入的細節後續章節會講):
這裡主要是把 class 放入方法區、還有 class 中的靜態變數和常量也要放入方法區
4. 執行方法及建立物件:
啟動 main 執行緒,執行 main 方法,開始執行第一行程式碼。此時堆記憶體中會建立一個 student 物件,物件引用 student 就存放在棧中。
後續程式碼中遇到 new 關鍵字,會再建立一個 student 物件,物件引用 student 就存放在棧中

總結一下   JVM 執行記憶體的整體流程
JVM 在作業系統上啟動,申請記憶體,先進行執行時資料區的初始化,然後把類載入到方法區,最後執行方法。
方法的執行和退出過程在記憶體上的體現上就是虛擬機器棧中棧幀的入棧和出棧。
同時在方法的執行過程中建立的物件一般情況下都是放在堆中,最後堆中的物件也是需要進行垃圾回收清理的。
 
堆空間分代劃分
堆被劃分為新生代和老年代( Tenured ),新生代又被進一步劃分為 Eden Survivor 區,最後 Survivor From Survivor To Survivor 組成。
 
JHSDB 工具
JHSDB 是一款基於服務性代理實現的程序外除錯工具。服務性代理是 HotSpot 虛擬機器中一組用於對映 Java 虛擬機器執行資訊的,主要基於 Java 語言實現的 API 集合。
JDK1.8 的開啟方式
開啟 HSDB 工具 Jdk1.8 啟動 JHSDB 的時候必須將 sawindbg.dll (一般會在 JDK 的目錄下)複製到對應目錄的 jre 下(注意在 win 上安裝了 JDK1.8 後往往同級目錄下有一個 jre 的目錄)

JDK1.9 及以後的開啟方式
進入 JDK bin 目錄下,我們可以在命令列中使用 jhsdb hsdb 來啟動它
程式碼改造
VM 引數加入:
-XX:+UseConcMarkSweepGC

深入辨析堆和棧
功能
以棧幀的方式儲存方法呼叫的過程,並存儲方法呼叫過程中基本資料型別的變數( int short long byte float double boolean char 等)以
及物件的引用變數,其記憶體分配在棧上,變量出了作用域就會自動釋放;
而堆記憶體用來儲存 Java 中的物件。無論是成員變數,區域性變數,還是類變數,它們指向的物件都儲存在堆記憶體中;
執行緒獨享還是共享
棧記憶體歸屬於單個執行緒,每個執行緒都會有一個棧記憶體,其儲存的變數只能在其所屬執行緒中可見,即棧記憶體可以理解成執行緒的私有記憶體。
堆記憶體中的物件對所有執行緒可見。堆記憶體中的物件可以被所有執行緒訪問。
空間大小
棧的記憶體要遠遠小於堆記憶體
虛擬機器記憶體優化技術
棧的優化技術——棧幀之間資料的共享
在一般的模型中,兩個不同的棧幀的記憶體區域是獨立的,但是大部分的 JVM 在實現中會進行一些優化,使得兩個棧幀出現一部分重疊。(主要體現在方
法中有引數傳遞的情況),讓下面棧幀的運算元棧和上面棧幀的部分區域性變數重疊在一起,這樣做不但節約了一部分空間,更加重要的是在進行方法調 用時就可以直接公用一部分資料,無需進行額外的引數複製傳遞了

堆溢位
記憶體溢位:申請記憶體空間 , 超出最大堆記憶體空間。
如果是記憶體溢位,則通過 調大 -Xms -Xmx 引數。
如果不是記憶體洩漏,就是說記憶體中的物件卻是都是必須存活的,那麼久應該檢查 JVM 的堆引數設定,與機器的記憶體對比,看是否還有可以調整的空間,
再從程式碼上檢查是否存在某些物件生命週期過長、持有狀態時間過長、儲存結構設計不合理等情況,儘量減少程式執行時的記憶體消耗。
 
 
方法區溢位
1 執行時常量池溢位
2 方法區中儲存的 Class 物件沒有被及時回收掉或者 Class 資訊佔用的記憶體超過了我們配置。
注意 Class 要被回收,條件比較苛刻(僅僅是可以,不代表必然,因為還有一些引數可以進行控制):
1 、該類所有的例項都已經被回收,也就是堆中不存在該類的任何例項。
2 、 載入該類的 ClassLoader 已經被回收。
3 、 該類對應的 java.lang.Class 物件沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法
 
本機直接記憶體溢位
直接記憶體的容量可以通過 MaxDirectMemorySize 來設定(預設與堆記憶體最大值一樣),所以也會出現 OOM 異常;
由直接記憶體導致的記憶體溢位,一個比較明顯的特徵是在 HeapDump 檔案中不會看見有什麼明顯的異常情況,如果發生了 OOM ,同時 Dump 檔案很小,可
以考慮重點排查下直接記憶體方面的原因。

 

常量池分析

 

String 物件的不可變性
瞭解了 String 物件的實現後,你有沒有發現在實現程式碼中 String 類被 final 關鍵字修飾了,而且變數 char 陣列也被 final 修飾了。
我們知道類被 final 修飾代表該類不可繼承,而 char[] final+private 修飾,代表了 String 物件不可被更改。 Java 實現的這個特性叫作 String 物件的不
可變性,即 String 物件一旦建立成功,就不能再對它進行改變。
 
Java 這樣做的好處在哪裡呢?
第一, 保證 String 物件的安全性。假設 String 物件是可變的,那麼 String 物件將可能被惡意修改。
第二, 保證 hash 屬性值不會頻繁變更,確保了唯一性,使得類似 HashMap 容器才能實現相應的 key-value 快取功能。
第三, 可以實現字串常量池。在 Java 中,通常有兩種建立字串物件的方式,一種是通過字串常量的方式建立,如 String str= abc ”;另一種是
字串變數通過 new 形式的建立,如 String str = new
 

 

intern
String intern 方法,如果常量池中有相同值,就會重複使用該物件,返回物件引用。
1 new Sting() 會在堆記憶體中建立一個 a String 物件, king" 將會在常量池中建立
2 、在呼叫 intern 方法之後,會去常量池中查詢是否有等於該字串物件的引用,有就返回引用。
3 、呼叫 new Sting() 會在堆記憶體中建立一個 b String 物件。
4 、在呼叫 intern 方法之後,會去常量池中查詢是否有等於該字串物件的引用,有就返回引用。 所以 a b 引用的是同一個物件