環境:java1.8 -Xms10m -Xmx10m -XX:+PrintGCDetails
最近在研究GC時,發生一些問題想了好久纔想明白,先上測試代碼。
import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
class OOMData {
private static ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<>();
private static ArrayList<String> list = new ArrayList<>();
public void testString() {
String a = "www.baidu.com";
while (true) {
a += (new Random().nextInt(888888888) + new Random().nextInt(999999999)
+ getRandomString(123) + getRandomString(345)
+ getRandomString(789) + "哈哈哈哈哈啊哈哈哈哈"
+ getRandomString(234) );
}
}
public void testStaticMap() {
int i = 1;
String a = "test";
while (true) {
map.put(i++, getRandomString(1000));
}
}
public void testStaticList() {
while (true) {
list.add("test");
}
}
//length用戶要求產生字符串的長度
public static String getRandomString(int length){
String str="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Random random=new Random();
StringBuffer sb=new StringBuffer();
for(int i=0;i<length;i++){
int number=random.nextInt(62);
sb.append(str.charAt(number));
}
return sb.toString();
}
}
public class OOMTest {
public static void main(String[] args) {
OOMData data = new OOMData();
data.testStaticList();
}
}
下面上測試結果:
運行testString()方法拋出的異常爲:(即測試字符串方法,jdk1.8以後將字符串常量池轉移到堆中,每次字符串改變後都會在堆中新建一個字符串對象,直至內存溢出,這個GC日誌分析請移步https://blog.csdn.net/weixin_38342534/article/details/102182145)
java.lang.OutOfMemoryError: Java heap space
運行testStaticMap()方法後拋出的異常爲:
java.lang.OutOfMemoryError: GC overhead limit exceeded
運行testStaticList()方法拋出的異常爲:
java.lang.OutOfMemoryError: Java heap space
下面這兩個我剛開始不是很理解,因爲我都使用了static進行修飾,被static修飾的類會存放在元空間,不會佔用堆內存,而元空間的大小理論上跟筆記本的物理內存一樣大。且報錯並不是java.lang.OutOfMemoryError: Metaspace 一個是java.lang.OutOfMemoryError: GC overhead limit exceeded 即程序用98%的時間回收了不到2%的堆內存。一個是java.lang.OutOfMemoryError: Java heap space即堆old區滿了。貼出GC日誌
testStaticList
testStaticMap
通過GC日誌可以看出兩個都是因爲堆內存被佔滿導致的OOM,說下原因是因爲ArrayList底層是使用的數組,而這個數組是ArrayList定義的成員變量Object[],成員變量是存在堆內存中的,所以纔會java heap space。而ConcurrentHashMap底層的鏈表和紅黑樹結構也都是在類中定義的成員變量,而每次擴展也都會生成一些新的變量,(底層原理具體請看源碼或者別的博客)所以最後young區和old區全部被佔滿。
這說明,被static修飾的容器容易導致oom不是因爲存入了元空間,元空間內存不夠導致的,而是因爲被static修飾的容器,是GC Roots的對象之一,在它下面進行可達性分析的時候,會一直可達,所以會導致GC的時候,不能被回收,從而導致OOM