標籤:並且 tool 存在 運行 自身 flow 輸出 添加元素 異常
這次看一些關於JVM記憶體分析的內容。
首先來看兩個程式,這裡是程式一:JVMStackTest,看下代碼:
package com.zhyea.robin.jvm;public class JVMStackTest { private static int count = 0; private void recur() { ++count; recur(); System.out.println("recur()方法執行結束"); } public static void main(String[] args) { try { new JVMStackTest().recur(); } catch (Throwable e) { e.printStackTrace(); } finally { System.out.println("程式遞迴了 " + count + "次"); } }}
這個類很簡單了。JVMStackTest的主體是一個recur()方法。recur()方法是一個遞迴的方法。所謂遞迴的方法就是在方法體中又調用了方法自身。在recur()方法中引用了一個類成員變數count來記錄遞迴的次數,並且做了一段輸出來提示方法執行結束。JVMStackTest的main方法內容也很少,就是建立了一個JVMStackTest的執行個體並調用recur()方法,同時使用try{}catch(){}來捕獲可能發生的異常或錯誤,最後在程式執行結束前輸出recur()方法遞迴的次數。
這個程式是有些問題的。問題出在recur()方法這裡:recur()是一個遞迴的方法,但是卻沒有跳出的語句,這意味著recur()方法將無限的遞迴下去。我們需要認識到一點:資源是有限的,如果某個服務無限次的佔用資源卻始終不釋放資源就必然會導致問題。每次調用recur()方法都會佔用一些資源,資源佔用得多了就會導致可用資源不足產生異常或錯誤。運行下這個程式,看看會報什麼樣的錯誤:
在遞迴了1862次以後報了StackOverflowError,即棧溢出錯誤。請大家先記住這個錯誤資訊,稍後我們會解釋為什麼會報這個錯。
再來看看程式二,JVMHeapTest:
package com.zhyea.robin.jvm;import java.util.LinkedList;import java.util.List;public class JVMHeapTest { private static List<byte[]> list = new LinkedList<>(); private void loop() { while (true) { list.add(new byte[1024 * 1024]); } } public static void main(String[] args) { try { new JVMHeapTest().loop(); } catch (Throwable t) { t.printStackTrace(); } finally { System.out.println("list 的長度是:" + list.size()); } }}
這個JVMHeapTest也是很簡單了,就是定義了一個List列表型的類成員變數list,通過泛型指定list中儲存的單元必須是數組,而後通過loop()方法向list中不停地添加數組執行個體。每個數組的大小為1024,也就是1KB大小。main方法也和JVMStackTest差不多,就是建立一個類執行個體來調用loop方法,通過try/catch捕獲可能會發生的異常,最後在程式執行結束的時候輸出list的長度。
看看loop()方法,很明顯地可以看出這個類也是存在問題的:這個類中的loop()方法有一個死迴圈,尤其是在這個死迴圈中還在不停向一個類成員列表添加元素。當添加的元素大小總和超過了某個上限後就會報錯。具體是什麼錯誤呢,運行一下看看:
正如預料,發生了錯誤。發生錯誤時列表的長度是7,報的錯是OutOfMemoryError,根據錯誤資訊“Java heap space”可以看出OutOfMemory錯誤是發生在heap上,也就是堆上。
通過這兩個程式我們示範了兩種常見的記憶體錯誤:StackOverflow和OutOfMemory,即棧溢出和記憶體溢出。這兩個錯誤的發生有一個共同點:無限制的佔用系統某一部分資源(程式一的無限遞迴,程式二死迴圈),以至於超過了某個上限。我們需要弄明白的就是這裡說的資源是什麼資源,而上限又具體是多少。
關於資源,我們前面反覆提到過幾次,主要就是記憶體。不過相較於我們通常提到的記憶體,這裡說的記憶體還分得更細一些。我們通常說筆記本有4G記憶體、8G記憶體,就是筆記本的機器記憶體,這個記憶體被稱作是直接記憶體或實體記憶體。而我們啟動一個Java程式就會從直接記憶體中擷取一塊讓JVM獨立使用的空間,這塊空間就可以被稱為是JVM記憶體。而JVM記憶體又可以被粗略地分成兩大類:棧記憶體和堆記憶體。如:
注意我們這裡將記憶體分成棧記憶體和堆記憶體是一種很粗略的分法,至於準確的分法我們稍後會單獨說。
看一下,左側的細長條標識棧記憶體,右側的橢圓標識記憶體。通常棧記憶體要比堆記憶體小得多。不知道有沒有注意到棧記憶體的標示圖中還有更細小的小長方形。這個小長方形標識的是棧幀。棧幀是棧記憶體的最小單位。
##########################
Java記憶體分析1 - 從兩個程式說起