环境: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