標籤:
在記憶體管理方面,Java相對於C和C++的區別在於Java具有記憶體動態分配以及垃圾收集技術,但平時我們很少去關注JVM的記憶體結構以及GC,在出現記憶體泄露或溢出方面的問題,排查工作將變得異常艱難。
1. 運行時資料區域 Java虛擬機器在執行Java程式時會將其管理的記憶體按照用於劃分為若干個不同的資料區域,這些地區有著各自不同的生命週期。根據《JAVA虛擬機器規範》,Java虛擬機器管理的記憶體會包含以下幾個地區。其中可以分為共用記憶體區以及線程隔離資料區兩個部分。 2. 程式計數器 程式計數器是一個非常小的記憶體空間,可以看做當前線程執行的位元組碼的行號指標。在虛擬機器中,程式的執行,跳轉,迴圈等都需要該計數器的值來選取下一條要執行的指令。程式計數器佔用的記憶體空間非常小,而且通常不會出現OOM或SOF等。 但需要注意的是,程式計數器如果正在執行一個Java方法,則其中儲存的是下一條指令的地址;如果執行的是Natvie方法,則為空白。 3. JAVA 虛擬機器棧 同程式計數器一樣,虛擬機器棧也是線程私人的,其周期與線程的生命週期相同,其描述的是Java方法執行的記憶體模型:每個方法執行的時候都會同時建立一個棧幀用於儲存局部變數表、操作棧、動態連結、方法出口等資訊。每一個方法被調用直到執行完成的過程就對應著一個虛擬機器棧從入棧到出棧的過程。 Java記憶體區通常習慣被分為堆和棧,這種劃分方法實際上是比較粗略的,此時所謂的棧就是虛擬機器棧。 局部變數表存放了編譯期可知的基礎資料型別 (Elementary Data Type),對象引用和returnAddress類型。 需要注意的是,局部變數表所需的記憶體空間在編譯期內完成分配,當進入一個方法時,這個方法在幀中分配多大的記憶體空間是完全確定的,在運行時也不會改變。 在JAVA虛擬機器規範中,定義了兩種異常:如果線程請求深度大於虛擬機器允許深度,則會拋出StackOverflowError異常;如果擴充無法申請到足夠記憶體時會拋出OutOfMemoryError異常。 由-Xss設定棧大小
// 虛擬機器棧和本地方法棧的大小 = 線程允許最大記憶體 - 最大堆容量 - 最大方法區容量// 在多線程時,給每個線程分配的棧越大,越容易出現異常
4. 本地方法棧 本地方法棧只為虛擬機器使用的Native方法服務,其他類似於虛擬機器棧 5. 堆 Java Heap是java記憶體管理中最大的一部分,Java堆是被所有線程共用的一塊記憶體地區,在虛擬機器啟動時建立。此記憶體地區的唯一目的就是存放對象的執行個體。 Java Heap是GC的主要區域,有時候會被成為“GC堆”。Java堆還可以進行細分:新生代和老年代:再細緻一點可以劃分為Eden空間、From Survivor空間、To Survivor空間等。雖然被劃分為多個空間,其存放的仍是對象,目的是為了更好更快的回收記憶體。 Java堆可以位於不連續的記憶體空間上,並且可以通過配置進行擴充 -Xmx 和-Xms控制,如果在堆中沒有完成記憶體配置,而且也無法擴充時,會拋出"OutOfMemoryError“異常。 樣本:
// 堆溢出// -Xmx 堆最大值// -Xms 堆最小值// -XX:+HeapDumpOnOutOfMemoryError 設定虛擬機器在異常時轉儲當前記憶體堆// 可以使用Eclipse Memory Analyzer 對轉儲的堆進行分析
6. 方法區 方法區也是線程共用的記憶體地區,用於儲存載入的類,常量,靜態變數和即時編譯器編譯後的代碼。 對於使用HotSpot虛擬機器的開發人員來說,其通常將其稱為永久代,意為方法區內較少出現垃圾收集,主要是對常量的清理和類的卸載,但回收的記憶體的效果較其他記憶體地區要差。 7. 運行時常量池 Runtime constant pool是方法區的一部分,其中會儲存在class檔案中的字面量和符號引用,常量池在運行時也可以將資料匯入 8. 直接記憶體 直接記憶體並非虛擬機器運行記憶體的一部分,是屬於運行環境的記憶體地區。 如JAVA引入的NIO,可以直接操作本機記憶體,雖然不再受JVM堆大小的限制,但仍然受運行環境記憶體限制,當超限時,也會拋出OOM異常。 9. 對象訪問 Object obj = new Object() Object obj 將會反映在Java棧的本地變數表中,作為一個reference類型資料出現。 new Object 將會反應在堆中,並分配一塊堆的結構化記憶體 java訪問對象可能採用直接存取後者控制代碼訪問兩種方式,其中直接存取方式速度快,控制代碼訪問稍慢,但控制代碼中存放穩定的控制代碼地址。
深入理解Java虛擬機器 - 虛擬機器記憶體劃分