从main方法分析内存溢出

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

image.png

概念先行

JVM内存模型(JMM):
堆,方法区,本地方法栈,虚拟机栈,程序计数器 (后面三个线程共享
栈和堆:
栈是运行空间,堆是存储空间,类似于我小米手机的运行内存(RAM)8G和存储空间(ROM)128G。
java中基本类型和堆对象的引用存在栈中。
堆:
堆在JVM中占据了很大的空间,用来存放实例对象,等会儿我们就拿它下手!
堆:我当时害怕急了。
堆内存分为年轻代和老年代,java8之后没有了永久代。(往细了说年轻代还有伊甸园(eden)和两个幸存区(from、to))
内存溢出
内存溢出有五种:

  1. java.lang.OutOfMemoryError: java heap space
  2. java.lang.OutOfMemoryError: GC over head limit exceeded
  3. java.lang.OutOfMemoryError: PermGen space
  4. java.lang.OutOfMemoryError: Direct buffer memory
  5. java.lang.StackOverflowError

java heap space:
当java对象在年轻代存活一段时间经历过N次回收没有被回收掉后(N还可以自己设置),就会进入老年代,在老年代中又经历回收后积累的没有被回收的对象超负荷后就会抛出内存溢出的异常。

垃圾回收机制算法

  1. 标记清除算法
  2. 标记整理算法
  3. 复制算法

堆内存溢出

我把堆内存设置小一点先。
在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.logGC日志的生成路径和日志名称

加上后看到的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的情况。各种饼状图,柱状图,折线图给你安排的明明白白。

image.png

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

image.png

堆空间渐渐被占满

image.png

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

分析内存快照

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

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

使用jprofiler打开生成的快照文件(xxx.hprof),结果显而易见,People对象的锅,这样你就可以很方便的找出代码中哪里不洽当使用该对象的地方,精准定位,甩锅给同事了。
image.png
当然仅限于代码需要优化的情况,如果没有需要优化的还出现这种异常,就需要增大堆内存的空间-Xms-Xmx两个参数。
如果还不行就需要结合上面两个工具分析,做出更细化的调整。

JVM调优参数

-Xms初始分配大小,默认为物理内存的1/64
-Xmx初始最大分配内存,默认为物理内存的14

参数说明
-Xms4g堆最小值,和最大值设置一样为宜
-Xmx4g堆最大值
-Xmn2g年轻代大小
-Xss128k每个线程的堆栈大小
-XX:NewRatio=4年轻代和老年代的比例1:4
-XX:SurvivorRatio=4年轻代中Survivor区与Eden区与的大小比值1:4
-XX:MaxTenuringThreshold=15对象在年轻代能经历回收的次数
-XX:+UseseriaIGC使用串行GC收集器
-XX: +Use ParalleIGC使用并行GC收集器
-XX: +UseParallelOldGC使用 parallel old收集器
-XX: +Use ConcMarkSweepGC使用CMS收集器
阅读 178

推荐阅读
苟且偷生
用户专栏

大树的专栏

2 人关注
16 篇文章
专栏主页