棧內存溢出
棧內存錯誤包括:棧幀過多(StackOverflowError)、棧幀過大(OutOfMemoryError)
- StackOverflowError:如果線程請求的棧深度大於虛擬機所允許的最大深度;
- OutOfMemoryError:如果虛擬機的佔內存允許動態擴展,當擴展容量無法申請到足夠的內存時;
棧幀過多
/**
* 棧幀過多導致內存溢出演示
* java.lang.StackOverflowError
*
* -Xss256k 設置棧內存大小
*/
public class StackOverflowDemo {
private static int count;
public static void main(String[] args) {
try {
method1();
}catch (Throwable e){
e.printStackTrace();
System.out.println(count);
}
}
private static void method1() {
count++;
method1();
}
}
棧幀過大
/**
* 棧幀過大導致內存溢出演示
* Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
*
* -Xss256k 設置棧內存大小
*
*《Java虛擬機規範》明確允許Java虛擬機實現自行選擇是否支持棧的動態擴展, 而HotSpot虛擬機的選擇不支持擴展;
* 結論:無論式由於棧幀太大還是虛擬機容量太小, 當新的棧幀內存無法分配的時候, HotSpot虛擬機拋出的都是StackOverflowError異常;
* 若在允許動態擴展棧容量大小的虛擬機上, 結果就會導致不一樣的錯誤; 如Classic虛擬機, 則會出現java.lang.OutOfMemoryError;
* 若測試不限於單線程, 通過不斷建立線程的方式, 在HotSpot也可以產生內存溢出異常;代碼如下, 現象不容易出現;
* 注: Java的線程時映射到操作系統的內核線程上的, 因此代碼執行時有較大風險, 可能會導致操作系統假死;
*/
public class Demo {
private void dontStop() {
while (true) {
}
}
public void stackLeakByThread() {
while (true) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
dontStop();
}
});
thread.start();
}
}
public static void main(String[] args) throws Throwable {
Demo oom = new Demo();
oom.stackLeakByThread();
}
}
堆內存溢出
堆內存溢出包括:Java堆(Heap)內存溢出、元空間內存溢出、運行時常量池溢出、本機直接內存溢出;
Java 堆內存溢出
/**
* 演示堆內存溢出
* java.lang.OutOfMemoryError: Java heap space
*
* -Xmx8m
*/
public class Demo {
public static void main(String[] args) {
int i = 0;
try {
List<String> list = new ArrayList<>();
String a = "hello";
while (true) {
list.add(a); // hello, hellohello, hellohellohellohello ...
a = a + a; // hellohellohellohello
i++;
}
} catch (Throwable e) {
e.printStackTrace();
System.out.println(i);
}
}
}
方法區溢出:永久代內存溢出、元空間內存溢出
- 永久代內存溢出(JDK1.6)
演示永久代內存溢出 java.lang.OutOfMemoryError: PermGen space
-XX:MaxPermSize=8m
import com.sun.xml.internal.ws.org.objectweb.asm.ClassWriter;
import com.sun.xml.internal.ws.org.objectweb.asm.Opcodes;
- 元空間內存溢出(JDK1.8)
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Opcodes;
/**
* 演示元空間內存溢出
*
* java.lang.OutOfMemoryError: Metaspace
*
* -XX:MaxMetaspaceSize=8m
*/
public class Demo extends ClassLoader { // 可以用來加載類的二進制字節碼
public static void main(String[] args) {
int j = 0;
try {
Demo test = new Demo();
for (int i = 0; i < 10000; i++, j++) {
// ClassWriter 作用是生成類的二進制字節碼
ClassWriter cw = new ClassWriter(0);
// 版本號, public, 類名, 包名, 父類, 接口
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
// 返回 byte[]
byte[] code = cw.toByteArray();
// 執行了類的加載
test.defineClass("Class" + i, code, 0, code.length); // Class 對象
}
} finally {
System.out.println(j);
}
}
}
直接內存溢出
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
/**
* 演示直接內存溢出
*
* -Xmx6M
*/
public class Demo {
static int _100Mb = 1024 * 1024 * 100;
public static void main(String[] args) {
List<ByteBuffer> list = new ArrayList<>();
int i = 0;
try {
while (true) {
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_100Mb);
list.add(byteBuffer);
i++;
}
} finally {
System.out.println(i);
}
// 方法區是jvm規範
// jdk6 中對方法區的實現稱爲永久代
// jdk8 對方法區的實現稱爲元空間
}
}