记一次模拟jvm的OOM

时间:2021-03-05 20:55:21

模拟一次jvm的OOM异常

在学习jvm的时候,听前辈建议自己手打一遍周志明大侠写的深入理解jvm虚拟机上的代码。这本书要多看几遍,看了是1年前,那个时候刚接触java也就1年,看的是云里雾里。现在看了第二遍,能理解一些jvm中内存分区,gc的分类,基本异常造成的原因,其中尝试openjdk下载失败了几次,下一次读的时候把这个作为点。

代码

代码和jvm的参数都是来自于周大侠的书,本次gc回收器采用默认的,没有设置。直接贴上。

/**
* Created by carey on 2016/7/9 0009.
* jvm运行参数
* -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
* 堆大小设置为20M,新生代10M,新生代中可用的是9M,剩余1M用来垃圾收集器回收的交换区
*
*/
public class OutOfMemory {
static class OOMObject{

}

public static void main(String[] args) {
List<OOMObject> list = new ArrayList<OOMObject>();
while(true){
list.add(new OOMObject());
}

}
}

启动参数的含义为堆大小固定,20M。其中新生代10M,老生代剩下10M,新生代中8:1:1的配置,实际可用9M

日志分析

不同机子,打印出来的日志肯定不一样。我以自己的为例子。

第一条输出的小gc日志

[GC [PSYoungGen: 7768K->1016K(9216K)] 7768K->5289K(19456K), 0.0129323 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]

具体分析:

这次的小gc实际时间为0.01s

       新生代gc:垃圾收集工作前可用的空间为7768K,收集后剩下了1016K,回收了6752K
位置 工作前被占用的空间 工作后被占空的空间 总共的空间 回收的空间
新生代: 7768K 1016K 9216K 6752K
整个堆 7768K 5289K 19456K 2479K(这才是真正被回收掉的空间)
新生代被回收的空间 —— 整个堆回收的空间 = 新生代被交换到老生带的空间。
总共的空间和jvm设置的参数是匹配的。这一次小gc有2479K真的被回收了,有4273K从新生代转到了老生代。### 第一条输出的大gc日志`[Full GC [PSYoungGen: 9208K->0K(9216K)] [ParOldGen: 10232K->9816K(10240K)] 19440K->9816K(19456K) [PSPermGen: 3042K->3041K(21504K)], 0.3411768 secs] [Times: user=0.45 sys=0.00, real=0.34 secs]`具体分析:这次大gc实际时间为0.34s。大gc的时候明显是大于小gc。
 
位置 工作前被占用的空间 工作后被占空的空间 总共的空间 回收的空间
新生代: 9208K 0K 9216K 9208K
老生代 10232K 9816K 10240K 416k
整个堆 19440K 9816K 19456K 9624K(这才是真正被回收掉的空间)
方法区 3042K 3041K 21504K 1K

新生代升级到老生代的空间为9208K+416K-9624k = 0

一次大gc肯定会引发一次小gc。大gc会回收方法区,而小gc不会。
从系统时间上来看,这次的大gc并不是第一次执行的大gc。

疑惑点

  1. 这段代码看上起没有可以被回收的变量,为什么还是会有空间能够被回收?
  2. gc的日志不是按照时间顺序来的,应该是会用队列或者数组来实现,难道是有多条线程用来打印?
  3. 从后面几条的日志来看,一次大gc还没有完成,就开始了另外一次gc。
  4. 为什么在这里大gc中方法区也能被回收?

如果有大神知道,请告诉小弟,不甚感激~