目录
1. 背景介绍
C或者C++的内存申请和销毁需要程序员自己控制,很容易内存泄漏和内存溢出且出现问题查找困难。Java在内存管理的优势在于jvm自己申请和销毁内存,不需要程序员关注内存问题,更专注于业务逻辑。那为什么我们还需要理解JVM的内存管理机制和原理呢?第一、作为有追求的程序猿应该追根刨底,做到知自知彼;第二、当内存管理成为系统性能瓶颈时或者出现内存泄漏、内存溢出问题,程序猿只有了解JVM的内存原理才能够去优化和查找内存问题。第三,我们可能会遇到因为jvm内存回收,正常业务出现卡顿。
由于jvm内容非常之多,这一小节只分析full gc场景产生的原因和如何解决
2. 什么是full gc
这个要从jvm如何管理内存说起,当代主流虚拟机(Hotspot VM)的垃圾回收都采用“分代回收”的算法。“分代回收”是基于这样一个事实:对象的生命周期不同,所以针对不同生命周期的对象可以采取不同的回收方式,以便提高回收效率。
① 新生代(Young Generation):大多数对象在新生代中被创建,其中很多对象的生命周期很短。每次新生代的垃圾回收(又称Minor GC)后只有少量对象存活,所以选用复制算法,只需要少量的复制成本就可以完成回收。
新生代内又分三个区:一个Eden区,两个Survivor区(一般而言),大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到两个Survivor区(中的一个)。当这个Survivor区满时,此区的存活且不满足“晋升”条件的对象将被复制到另外一个Survivor区。对象每经历一次Minor GC,年龄加1,达到“晋升年龄阈值”后,被放到老年代,这个过程也称为“晋升”。显然,“晋升年龄阈值”的大小直接影响着对象在新生代中的停留时间,在Serial和ParNew GC两种回收器中,“晋升年龄阈值”通过参数MaxTenuringThreshold设定,默认值为15。
② 老年代(Old Generation):在新生代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代,该区域中对象存活率高。老年代的垃圾回收(又称Major GC)通常使用“标记-清理”或“标记-整理”算法。整堆包括新生代和老年代的垃圾回收称为Full GC(HotSpot VM里,除了CMS之外,其它能收集老年代的GC都会同时收集整个GC堆,包括新生代)。
③ 永久代(Perm Generation):主要存放元数据,例如Class、Method的元信息,与垃圾回收要回收的Java对象关系不大。相对于新生代和年老代来说,该区域的划分对垃圾回收影响比较小。
各分区的大小对GC的性能影响很大。如何将各分区调整到合适的大小,这个很有讲究,涉及到jvm调优层面的内容,就不细讲了。(一般是参考活跃数据来设置各个区的大小)
讲了这么多,终于入题了,那么什么是full gc 呢,网上其实有很多解读,我们本文暂时以老年代的垃圾回收称之为full gc。(严格来说不算)
3. 垃圾回收算法
老年代垃圾回收算法,互联网企业一般都采取cms(Concurrent Mark Sweep) 因为其高并发、高响应的特点,CMS是基于标记-清除算法实现的。具体有哪些算法,这里不细讲,cms也是有缺点的:
a.CMS收集器对CPU资源非常敏感,在并发阶段虽然不会导致用户停顿,但是会占用CPU资源而导致应用程序变慢,总吞吐量下降。
b.CMS收集器无法处理浮动垃圾,可能出现“Concurrnet Mode Failure”,失败而导致另一次的Full GC。
c.CMS收集器是基于标记-清除算法的实现,因此也会产生碎片。
由于cms有这些缺点,而其他算法更不争气,于是出现了G1。相比CMS收集器有不少改进,首先,基于标记-压缩算法,不会产生内存碎片,其次可以比较精确的控制停顿。jdk1.9默认G1
垃圾算法选择依据:
曾经有讲过在heap size<=3G的情况下完全不要考虑CMS GC,在heap size>3G的情况下也优先选择ParallelOldGC,而不是CMS GC,只有在暂停时间无法接受的情况下才考虑CMS GC(不过当然,一般来说在heap size>8G后基本上都得选择CMS GC