筆記:深入理解JVM 第2章 Java記憶體區域與記憶體溢位
1、JVM 執行時資料區
所有執行緒共享的資料區:方法區(持久代)、堆區
執行緒隔離的資料區:程式計數器、Java虛擬機器棧區
堆區構成:新生代 ( 由Eden, From Survivor, To Survivor 構成)、老生代
執行時常量池:方法區一部分,用於存放編譯期生成的各種字面量和符號引用
直接記憶體:不是JVM 執行時資料區的一部分,不受Java堆大小限制,但是受實體記憶體限制,也會丟擲 OutOfMemoryError。Java NIO 中的DirectByteBuffer 使用了直接記憶體。
2、OutOfMemoryError 案例
(1)、堆溢位
在物件數量達到最大堆容量限制後,發生堆記憶體溢位異常 OutOfMemoryError:
JVM配置:
-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
程式碼:
public static void main(String[] args) {
List<Object> list = new LinkedList<Object>();
for(int i=0;i<Long.MAX_VALUE;i++)
{
list.add(new XX());
}
}
輸出:
java.lang.OutOfMemoryError: GC overhead limit exceeded Dumping heap to java_pid4192.hprof ... Heap dump file created [37582435 bytes in 0.265 secs] Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded at java.util.LinkedList.linkLast(Unknown Source) at java.util.LinkedList.add(Unknown Source) at chapter2.HeapOver.main(HeapOver.java:14)
使用Eclipse Memory Analyzer 分析生成的Dump檔案:java_pid4192.hprof
快照
(2).棧溢位
執行緒的請求棧深度大於虛擬機器所允許最大深度,將丟擲StackOverflowError
如果虛擬機器在擴充套件棧時候無法申請得到足夠的記憶體空間,將丟擲OutOfMemoryError
JVM配置:
-Xss128k
單執行緒程式碼:
public class StackOverExam1 { public int stackLength = 1; public void methed1() { System.out.println("Stack Length:" + stackLength++); if(stackLength < Long.MAX_VALUE) { methed1(); } } public static void main(String[] args) { StackOverExam1 exam = new StackOverExam1(); exam.methed1(); } }
丟擲異常:
Stack Length:987
Stack Length:988
Exception in thread "main" java.lang.StackOverflowError
at java.nio.Buffer.<init>(Unknown Source)
at java.nio.CharBuffer.<init>(Unknown Source)
at java.nio.HeapCharBuffer.<init>(Unknown Source)
at java.nio.CharBuffer.wrap(Unknown Source)
at sun.nio.cs.StreamEncoder.implWrite(Unknown Source)
at sun.nio.cs.StreamEncoder.write(Unknown Source)
在單執行緒下,無論是棧幀太大還是虛擬機器容量太小,當記憶體無法分配時候,虛擬機器丟擲的都是StackOverflowError。
多執行緒情況下,-Xss設定得越大,即為每個執行緒棧分配記憶體越大,越容易丟擲 OutOfMemoryError: unable to create new native thread
多執行緒時候JVM配置:-Xss2m
多執行緒程式碼:
public static void main(String[] args) {
for(long i=0;i<Long.MAX_VALUE;i++)
{
System.out.println("Thread Num:"+ (++i));
new Thread(new Runnable()
{
@Override
public void run() {
while(true)
{
}
}
}).start();
}
}
丟擲異常:
ava.lang.OutOfMemoryError: unable to create new native thread
(3). 物件常量池和方法區溢位
丟擲 OutOfMemory: PermGen space
常量池溢位例子:
String.intern() 是一個Native方法,其作用是:若字串常量池包含此String物件,則返回該物件;否則將其加入到常量池中,再返回。
JVM配置:
-XX:PermSize=10m -XX:MaxPermSize=10m
程式碼:
public class RuntimeConstPoolOver {
public static void main(String[] args) {
List<String> list = new LinkedList<String>();
for (int i = 0; i < Long.MAX_VALUE; i++) {
//System.out.println(i);
list.add(String.valueOf(i).intern());
}
}
}
JDK1.6 輸出:
java.lang.OutOfMemoryError: PermGen space
JDK 1.7 不會有錯誤方法區溢位例子:
Spring、Hibernate 等框架會使用CGLib技術,將動態生成的Class載入方法區,當方法區太小時,引發
java.lang.OutOfMemoryError: PermGen space
(4).直接記憶體溢位
DirectMemory 容量可通過 -XX: MaxDirectMemorySize 制定,若無制定則與JVM堆的最大值(-Xmx) 一樣。
判斷是否由DirectMemory的溢位條件:
a. Heap Dump 檔案不會看到明顯異常,檔案很小
b. 程式中直接或間接使用了NIO
例子JVM配置:
-Xmx20M -XX:MaxDirectMemorySize=20M
程式碼:
import java.lang.reflect.Field;
import sun.misc.Unsafe;
public class DirectMemoryOver {
public static void main(String[] args) throws IllegalArgumentException,
IllegalAccessException {
long _100M = 100 * 1024 * 1024;
Field f = Unsafe.class.getDeclaredFields()[0];
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
for (int i = 0; i < Long.MAX_VALUE; i++) {
System.out.println(i);
unsafe.allocateMemory(_100M);
}
}
}
輸出:
126
Exception in thread "main" java.lang.OutOfMemoryError
at sun.misc.Unsafe.allocateMemory(Native Method)
at chapter2.DirectMemoryOver.main(DirectMemoryOver.java:17)