从main方法分析内存溢出

5次阅读

共计 7844 个字符,预计需要花费 20 分钟才能阅读完成。

内存溢出 OutOfMemoryError 不常遇到,起码没有姨妈空指针异常 (NullPointerException) 来的那么频繁。
现在就用最简单的 main 方法复现堆内存溢出并做分析。

概念先行

JVM 内存模型 (JMM):
堆,方法区,本地方法栈,虚拟机栈,程序计数器( 后面三个线程共享
栈和堆:
栈是运行空间,堆是存储空间,类似于我小米手机的运行内存(RAM)8G 和存储空间(ROM)128G。
java 中基本类型和堆对象的引用存在栈中。
堆:
堆在 JVM 中占据了很大的空间,用来存放实例对象,等会儿我们就拿它下手!
堆:我当时害怕急了。
堆内存分为年轻代和老年代,java8 之后没有了永久代。(往细了说年轻代还有伊甸园 (eden) 和两个幸存区(from、to))
当 java 对象在年轻代存活一段时间经历过 N 次回收没有被回收掉后(N 还可以自己设置), 就会进入老年代,在老年代中又经历回收后积累的没有被回收的对象超负荷后就会抛出内存溢出的异常。


图做的有点粗糙,还请见谅!

我把堆内存设置小一点先。
在 idea 的配置 (VM options)加上启动参数 -Xms10m -Xmx20m。
运行以下代码, 不断的生成 People 对象并放入集合中防止被回收。

public static void main(String[] args) {List<People> peoples = new ArrayList<>();
    int i = 0;
    while (true) {People abc = new People();
        i++;
        peoples.add(abc);
        System.out.println(abc.toString() + i);
    }
}

结果在生成 540217 个对象的时候抛出了内存溢出的异常。

......
People{name='null', sex='null'}540216
People{name='null', sex='null'}540217
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:3210)
    at java.util.Arrays.copyOf(Arrays.java:3181)
    at java.util.ArrayList.grow(ArrayList.java:265)
    at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239)
    at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231)
    at java.util.ArrayList.add(ArrayList.java:462)
    at com.example.demo.DemoMain.main(DemoMain.java:17)

开启 GC 日志

添加启动参数,不同的参数打印不同格式的日志。

参数 说明
-XX:-PrintGC 开启 GC 日志
-XX:-PrintGCDetails 打印详细信息
-XX:+PrintGCDateStamps 打印时间
-Xloggc:./gclogs.log GC 日志的生成路径和日志名称

加上后看到的 GC 日志如下, 太多了,我截取了后半段, 可以看到短期内 GC 之后都是 Full GC,
应该是老年代满了,触发 Full GC 但是还是有大量的对象不能被回收,最后抛出 OOM 的异常。
年轻代满了会触发 GC,也叫 Minor GC,老年代满了会触发 Full GC,CPU 空闲时也会回收垃圾,
JVM 调优的目的就是减少 GC 的频率和 Full GC 的次数。

2020-06-02T22:30:54.038+0800: 4.966: [GC (Allocation Failure)  12088K->7608K(15872K), 0.0046395 secs]
2020-06-02T22:30:54.151+0800: 5.079: [GC (Allocation Failure)  12216K->7800K(15872K), 0.0055183 secs]
2020-06-02T22:30:54.229+0800: 5.158: [GC (Allocation Failure)  12408K->9383K(15872K), 0.0062808 secs]
2020-06-02T22:30:54.347+0800: 5.275: [GC (Allocation Failure)  13991K->9551K(15872K), 0.0045744 secs]
2020-06-02T22:30:54.467+0800: 5.394: [GC (Allocation Failure)  14159K->9751K(15872K), 0.0054287 secs]
2020-06-02T22:30:54.472+0800: 5.400: [Full GC (Ergonomics)  9751K->8508K(19456K), 0.1291069 secs]
2020-06-02T22:30:54.714+0800: 5.642: [GC (Allocation Failure)  13116K->8796K(19456K), 0.0035147 secs]
2020-06-02T22:30:54.826+0800: 5.754: [GC (Allocation Failure)  13404K->8988K(19456K), 0.0054200 secs]
2020-06-02T22:30:54.941+0800: 5.869: [GC (Allocation Failure)  13596K->9084K(19456K), 0.0070004 secs]
2020-06-02T22:30:55.076+0800: 6.005: [GC (Allocation Failure)  13692K->9340K(19456K), 0.0082953 secs]
2020-06-02T22:30:55.206+0800: 6.134: [GC (Allocation Failure)  13948K->9468K(18432K), 0.0099645 secs]
2020-06-02T22:30:55.311+0800: 6.240: [GC (Allocation Failure)  13052K->9604K(18944K), 0.0110139 secs]
2020-06-02T22:30:55.404+0800: 6.332: [GC (Allocation Failure)  13188K->9780K(18944K), 0.0114867 secs]
2020-06-02T22:30:55.508+0800: 6.436: [GC (Allocation Failure)  13364K->9988K(18944K), 0.0095337 secs]
2020-06-02T22:30:55.600+0800: 6.528: [GC (Allocation Failure)  13572K->10052K(18944K), 0.0077667 secs]
2020-06-02T22:30:55.697+0800: 6.625: [GC (Allocation Failure)  13636K->10308K(18944K), 0.0076699 secs]
2020-06-02T22:30:55.789+0800: 6.717: [GC (Allocation Failure)  13892K->10460K(18944K), 0.0044046 secs]
2020-06-02T22:30:55.876+0800: 6.804: [GC (Allocation Failure)  14044K->10580K(17920K), 0.0046781 secs]
2020-06-02T22:30:55.965+0800: 6.892: [GC (Allocation Failure)  14164K->10764K(18432K), 0.0047827 secs]
2020-06-02T22:30:56.057+0800: 6.985: [GC (Allocation Failure)  14348K->10892K(18432K), 0.0050403 secs]
2020-06-02T22:30:56.171+0800: 7.100: [GC (Allocation Failure)  14476K->11036K(18432K), 0.0052502 secs]
2020-06-02T22:30:56.283+0800: 7.211: [GC (Allocation Failure)  15132K->11188K(18944K), 0.0052689 secs]
2020-06-02T22:30:56.340+0800: 7.267: [GC (Allocation Failure)  15284K->13394K(19456K), 0.0086552 secs]
2020-06-02T22:30:56.348+0800: 7.276: [Full GC (Ergonomics)  13394K->11645K(19456K), 0.2421109 secs]
2020-06-02T22:30:56.703+0800: 7.631: [GC (Allocation Failure)  16253K->11965K(19456K), 0.0045707 secs]
2020-06-02T22:30:56.821+0800: 7.749: [GC (Allocation Failure)  16573K->12125K(19456K), 0.0061328 secs]
2020-06-02T22:30:56.943+0800: 7.871: [GC (Allocation Failure)  16733K->12253K(19456K), 0.0067886 secs]
2020-06-02T22:30:57.066+0800: 7.994: [GC (Allocation Failure)  16861K->12509K(19456K), 0.0097933 secs]
2020-06-02T22:30:57.192+0800: 8.121: [GC (Allocation Failure)  17117K->12677K(19456K), 0.0115250 secs]
2020-06-02T22:30:57.312+0800: 8.241: [GC (Allocation Failure)  17285K->12789K(18432K), 0.0135347 secs]
2020-06-02T22:30:57.416+0800: 8.344: [GC (Allocation Failure)  16373K->12957K(18944K), 0.0105043 secs]
2020-06-02T22:30:57.516+0800: 8.445: [GC (Allocation Failure)  16541K->13093K(18944K), 0.0103981 secs]
2020-06-02T22:30:57.620+0800: 8.548: [GC (Allocation Failure)  16677K->13237K(18944K), 0.0098493 secs]
2020-06-02T22:30:57.729+0800: 8.657: [GC (Allocation Failure)  16821K->13421K(18944K), 0.0087252 secs]
2020-06-02T22:30:57.738+0800: 8.666: [Full GC (Ergonomics)  13421K->13218K(18944K), 0.1904231 secs]
2020-06-02T22:30:58.021+0800: 8.949: [Full GC (Ergonomics)  16802K->13300K(18944K), 0.2117216 secs]
2020-06-02T22:30:58.335+0800: 9.264: [Full GC (Ergonomics)  16884K->13435K(18944K), 0.1494933 secs]
2020-06-02T22:30:58.606+0800: 9.533: [Full GC (Ergonomics)  17019K->13569K(18944K), 0.1366324 secs]
2020-06-02T22:30:58.838+0800: 9.766: [Full GC (Ergonomics)  17153K->13703K(18944K), 0.1436044 secs]
2020-06-02T22:30:59.069+0800: 9.998: [Full GC (Ergonomics)  17287K->13837K(18944K), 0.1610447 secs]
2020-06-02T22:30:59.318+0800: 10.246: [Full GC (Ergonomics)  17402K->13971K(18944K), 0.1682002 secs]
2020-06-02T22:30:59.592+0800: 10.520: [Full GC (Ergonomics)  17402K->14099K(18944K), 0.1644734 secs]
2020-06-02T22:30:59.858+0800: 10.787: [Full GC (Ergonomics)  17402K->14223K(18944K), 0.2900750 secs]
2020-06-02T22:31:00.248+0800: 11.176: [Full GC (Ergonomics)  17402K->14342K(18944K), 0.1845255 secs]
2020-06-02T22:31:00.532+0800: 11.461: [Full GC (Ergonomics)  17402K->14457K(18944K), 0.1687967 secs]
2020-06-02T22:31:00.774+0800: 11.702: [Full GC (Ergonomics)  17402K->14567K(18944K), 0.2179055 secs]
2020-06-02T22:31:01.070+0800: 11.998: [Full GC (Ergonomics)  17402K->14674K(18944K), 0.2099567 secs]
2020-06-02T22:31:01.376+0800: 12.304: [Full GC (Ergonomics)  17402K->14776K(18944K), 0.1890367 secs]
2020-06-02T22:31:01.638+0800: 12.567: [Full GC (Ergonomics)  17402K->14874K(18944K), 0.1799833 secs]
2020-06-02T22:31:01.877+0800: 12.805: [Full GC (Ergonomics)  17402K->14969K(18944K), 0.1828141 secs]
2020-06-02T22:31:02.121+0800: 13.050: [Full GC (Ergonomics)  17402K->15060K(18944K), 0.1930089 secs]
2020-06-02T22:31:02.366+0800: 13.295: [Full GC (Ergonomics)  17402K->15148K(18944K), 0.1954160 secs]
2020-06-02T22:31:02.629+0800: 13.557: [Full GC (Ergonomics)  17402K->15232K(18944K), 0.1789831 secs]
2020-06-02T22:31:02.862+0800: 13.791: [Full GC (Ergonomics)  17402K->15313K(18944K), 0.1870638 secs]
2020-06-02T22:31:03.136+0800: 14.065: [Full GC (Ergonomics)  17402K->15392K(18944K), 0.2053623 secs]
2020-06-02T22:31:03.409+0800: 14.337: [Full GC (Ergonomics)  17402K->15467K(18944K), 0.1735414 secs]
2020-06-02T22:31:03.631+0800: 14.559: [Full GC (Ergonomics)  17402K->15539K(18944K), 0.1986520 secs]
2020-06-02T22:31:03.880+0800: 14.809: [Full GC (Ergonomics)  17402K->15609K(18944K), 0.1910945 secs]
2020-06-02T22:31:04.113+0800: 15.041: [Full GC (Ergonomics)  17402K->15676K(18944K), 0.1868629 secs]
2020-06-02T22:31:04.350+0800: 15.278: [Full GC (Ergonomics)  17402K->15741K(18944K), 0.2251443 secs]
2020-06-02T22:31:04.612+0800: 15.541: [Full GC (Ergonomics)  16188K->15757K(18944K), 0.2292204 secs]
2020-06-02T22:31:04.842+0800: 15.770: [Full GC (Allocation Failure)  15757K->15757K(18944K), 0.1922108 secs]

我把 GC 日志粘贴到这个分析 GC 日志的网站上,这个可视化工具可以帮我们更直观的欣赏 GC 的情况。各种饼状图,柱状图,折线图给你安排的明明白白。

选几个看一下,我的内存设置的最大 20M,可以看到峰值的时候是 16.9M

堆空间渐渐被占满

GC 和 Full GC 的回收的大小,时间。

分析内存快照

想要更详细的分析还得生成内存快照,同样添加启动参数

参数 说明
-XX:+HeapDumpOnOutOfMemoryError 开启内存快照
-XX:HeapDumpPath=./ 存储路径

使用 jprofiler 打开生成的快照文件(xxx.hprof),结果显而易见,People 对象的锅,这样你就可以很方便的找出代码中哪里不洽当使用该对象的地方,精准定位,甩锅给同事了。

当然仅限于代码需要优化的情况,如果没有需要优化的还出现这种异常,就需要增大堆内存的空间 -Xms-Xmx 两个参数。
如果还不行就需要结合上面两个工具分析,做出更细化的调整。

JVM 调优参数

参数 说明
-Xms4g 堆最小值,和最大值设置一样为宜
-Xmx4g 堆最大值
-Xmn2g 年轻代大小
-Xss128k 每个线程的堆栈大小
-XX:NewRatio=4 年轻代和老年代的比例 1:4
-XX:SurvivorRatio=4 年轻代中 Survivor 区与 Eden 区与的大小比值 1:4
-XX:MaxTenuringThreshold=15 对象在年轻代能经历回收的次数
正文完
 0