Java 堆空間
當涉及到JVM的內存處理時,Java堆空間是最常見的錯誤之一。這個錯誤意味著你試圖在 JVM 進程的堆上保留太多的數據并且沒有足夠的內存來創建新的對象,并且垃圾收集器無法收集到足夠的垃圾。
一個說明 Java 堆空間問題的示例:
public class JavaHeapSpace {
public static void main(String[] args) throws Exception {
String[] array = new String[100000 * 100000];
}
}
該代碼創建一個非常大的String對象數組,使用內存大小的默認設置,執行上述代碼時會看到以下結果:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at memory.JavaHeapSpace.main(JavaHeapSpace.java:5)
結果很簡單,堆上沒有足夠的內存來分配數組。
如何修復它: 在某些情況下,為了緩解問題,通過將-Xmx
添加到 JVM 應用程序啟動設置并將其設置為更大的值來增加最大堆大小就足夠了。
例如,要將最大堆大小增加到 8GB,可以將-Xmx8g
參數添加到JVM應用程序啟動參數中。
一般會在啟動JAVA進程的時候,設置JVM參數在遇到OOM時自動導出dump
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heap.hprof
1. HeapDumpOnOutOfMemoryError :當JVM發生OOM時,自動生成dump文件
2. HeapDumpPath : 生成dump文件的路徑 | 文件名稱
GC 開銷限制
GC Overhead Limit
正是它的名字所提示的,Java 虛擬機垃圾收集器無法回收內存的問題。將看到 java.lang.OutOfMemoryError: GC overhead limit exceeded
如果Java虛擬機將超過98%
的時間用于垃圾收集,連續5
次垃圾收集并且回收不到2%
的堆。
當使用垃圾收集的舊版本(如 Java 8)的舊 Java 版本時,可以通過運行類似于以下的代碼輕松模擬 GC Overhead 異常:
public class GCOverhead {
public static void main(String[] args) throws Exception {
Map<Long, Long> map = new HashMap<>();
for (long i = 0l; i < Long.MAX_VALUE; i++) {
map.put(i, i);
}
}
}
當運行一個小堆時,比如 25MB,會得到一個這樣的異常:
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.base/java.lang.Long.valueOf(Long.java:1211)
at memory.GCOverhead.main(GCOverhead.java:10)
這意味著堆幾乎已滿,垃圾收集器至少連續進行了 5 次垃圾收集,移除了不到 2% 的已分配對象。
如何修復它: 此類錯誤的解決方案是,通過將-Xmx
添加到JVM應用程序啟動設置,并將其設置為比當前使用的更大的值來增加堆。
數組大小限制
可能遇到的錯誤之一是java.lang.OutOfMemoryError: Requested array size exceeds VM limit
,它指出嘗試保留在內存中的數組大小大于Integer.MAX_INT
或者嘗試讓數組大于堆大小。
如何解決: 如果數組大于堆大小,可以嘗試增加堆大小。如果嘗試將超過2^31-1
個條目放入單個數組中,則需要修改代碼以避免這種情況。
線程問題數
當涉及到可以在單個應用程序中運行的線程數時,操作系統會受到限制。當看到java.lang.OutOfMemoryError: unable to create native thread
運行代碼的JVM拋出錯誤時,可以確定達到了限制,或者操作系統耗盡了資源來創建新線程。
為了說明創建線程的問題,創建一個連續創建線程并使它們進入睡眠狀態的代碼:
public class ThreadsLimits {
public static void main(String[] args) throws Exception {
while (true) {
new Thread(
new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000 * 60 * 60 * 24);
} catch (Exception ex) {}
}
}
).start();
}
}
}
運行上面的代碼之后,會拋出一個異常:
[0.420s][warning][os,thread] Failed to start thread - pthread_create failed (EAGAIN) for attributes: stacksize: 1024k, guardsize: 4k, detached.
Exception in thread "main" java.lang.OutOfMemoryError: unable to create native thread: possibly out of memory or process/resource limits reached
at java.base/java.lang.Thread.start0(Native Method)
at java.base/java.lang.Thread.start(Thread.java:802)
at memory.ThreadsLimits.main(ThreadsLimits.java:15)
可以清楚地看到,Java代碼已經耗盡了操作系統的限制,無法創建更多線程。
永久代問題
PermGen或永久代是 Java 堆中的一個特殊位置,Java 虛擬機使用它來跟蹤所有加載的類元數據、靜態方法、對靜態對象的引用和原始變量。PermGen已隨著 Java 8的發布而被刪除,因此在這一點上,可能永遠不會遇到它的問題。
PermGen的問題在于其有限的默認大小,在32位JVM
版本中為64MB
,在64位JVM
版本中最大為 82MB
。這是有問題的,因為如果應用程序包含大量類、靜態方法和對靜態對象的引用,很容易遇到PermGen空間太小的問題。
如何修復它: 如果遇到java.lang.OutOfMemoryError: PermGen space
錯誤,可以通過包含-XX:PermSize
和-XX:MaxPermSize
JVM參數來增加PermGen空間的大小。
元空間問題
隨著PermGen空間的移除,類元數據現在存在于本機空間中。保存類元數據的空間現在稱為Metaspace,是 Java虛擬機堆的一部分。
如何修復它: 當拋出錯誤時,Java 虛擬機會指示元空間區域的問題。java.lang.OutOfMemoryError: Metaspace
為了緩解這個問題,可以通過將標志添加到Java-XX:MaxMetaspaceSize
應用程序的啟動參數來增加元空間的大小。
例如,要將 Metaspace 區域大小設置為 128M,添加以下參數:-XX:MaxMetaspaceSize=128m
。
沒有交換
操作系統使用交換空間作為輔助內存來處理內存管理方案的分頁過程。當本機內存(RAM 和交換內存)接近耗盡時,Java 虛擬機可能沒有足夠的空間來創建新對象。這可能由于各種原因而發生系統過載,其他應用程序可能是大量內存用戶并且正在耗盡資源。在這種情況下JVM會拋出java.lang.OutOfMemoryError: Out of swap space
錯誤。
如何修復它: 發生此錯誤時,檢查操作系統交換設置,如果太低,請增加它。同時,需要驗證與應用程序在同一臺機器上是否有其他大量內存消耗者運行。