内存溢出和内存泄露
内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;
比如:内存中加载的数据量过于庞大,如一次从数据库取出过多数据;集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;代码中存在死循环或循环产生过多重复的对象实体等。
内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,故发生内存泄露。
比如:静态集合类引起内存泄露,像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Object也不能被释放,因为他们也将一直被Vector等引用着;当集合里面的对象属性被修改后,再调用remove()方法时不起作用;比如一个应用中使用多到监听器,但往往在释放对象的时候却没有记住去删除这些监听器,从而增加了内存泄漏的机会。
两者间的联系:
内存泄露会最终会导致内存溢出。
相同点:都会导致应用程序运行出现问题,性能下降或挂起。
不同点:
1) 内存泄露是导致内存溢出的原因之一,内存泄露积累起来将导致内存溢出。
2) 内存泄露可以通过完善代码来避免,内存溢出可以通过调整配置来减少发生频率,但无法彻底避免
JVM出现Full GC的情况
从新生代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC,对老年代GC称为Major GC,而Full GC是对整个堆来说的,Major GC的速度一般会比Minor GC慢10倍以上。
1、System.gc()方法的调用
程序员通过函数调用的形式,让JVM进行一次full gc的操作。尽量避免人工去进行full gc,最好让JVM自己去回收。
2、老年代空间不足
老年代空间的对象有两种形式,一种是有新生代满足minor gc次数后进入到老年代;另一种是创建了大的对象、大的数组,不经过新生代直接到老年代的形式。当老年代空间不足是就会触发full gc的情况。
不要创建过大的对象及数组,让对象在新生代中回收可以解决这种情况。
3、当新生代中要转移到老年代中的对象的大小低于老年代的剩余空间时
Hotspot为了避免由于新生代对象晋升到老年代导致老年代空间不足的现象,在进行Minor GC时,做了一个判断,如果之前统计所得到的Minor GC晋升到老年代的平均大小大于老年代的剩余空间,那么就直接触发Full GC。
4、方法区空间不足
运行时数据区域中的方法区用来存放类的信息、常量、静态变量等数据,当系统中要加载的类、反射的类和调用的方法较多时可能会被占满,在未配置为采用CMS GC的情况下也会执行Full GC。
可增大方法区空间或转为使用CMS GC。
5、CMS GC(Concurrent Mark Sweep GC)时出现promotion failed和concurrent mode failure
promotion failed是在进行Minor GC时,survivor space放不下、对象只能放入老年代,而此时老年代也放不下造成的;concurrent mode failure是在执行CMS GC的过程中同时有对象要放入老年代,而此时老年代空间不足造成的
可以增大survivor空间、老年代空间或调低触发并发GC的比例。
6、堆中分配很大的对象
这里的大对象是指需要大量连续内存空间的java对象,例如很长的数组,此种对象会直接进入老年代,而老年代虽然有很大的剩余空间,但是无法找到足够大的连续空间来分配给当前对象,此种情况就会触发JVM进行Full GC。
可通过配置CMS垃圾收集UseCMSCompactAtFullCollection开关参数。
总结:
Full GC可以清除老年代的垃圾本质上是好的行为,但是如果Full GC发生的频率高了,就会影响性能。同时,Full GC频繁发生,意味着你的内存分配机制存在问题,也许是内存泄露,有大量内存垃圾不断在老年代产生;也许是你的大对象(缓存)过多;也有可能是你的参数设置不好,minor GC清理不掉内存,导致每次minor GC都会触发Full GC;还有可能是你的老年代大小参数设置错误,老年代过小等等原因,合理排查处理才能最优化自己的代码。
最后推荐几篇full gc 排查过程的博客:
https://blog.csdn.net/fishinhouse/article/details/80781673