目录
新生代垃圾收集器(MinorGC/YoungGC)
老年代垃圾收集器(MajorGC/OldGC)
CMS垃圾收集器
G1通用垃圾收集器
混合收集(Mixed GC)
整堆收集(Full GC)
内存分配
命运之神自会在一个恰当的时间 把你引到该去的地方
只对新生代进行垃圾收集。
Serial垃圾收集器(单线程)
只开启一条GC线程进行垃圾回收并且在垃圾收集过程中停止一切用户线程。适用于客户端应用所需内存较小,不会创建太多对象,堆内存不大的情况。Serial收集器只使用一条GC线程,避免了线程切换的开销,从而简单高效,垃圾收集器回收时间短,即使在这段时间停止全部用户线程,也不会感觉明显卡顿。
ParNew垃圾收集器(多线程)
ParNew是Serial的多线程版本,由多条GC线程并行地进行垃圾清理。但清理过程依然需要停止一切用户线程。ParNew追求低停顿时间,与Serial唯一区别就是使用了多线程进行垃圾收集,在多 CPU环境下性能比Serial会有一定程度的提升,但线程切换需要额外的开销,所以在单CPU环境中不如Serial。
ParallelScavenge垃圾收集器(多线程)
Parallel Scavenge和ParNew 一样,都是多线程新生代垃圾收集器,但有两个不同点:
Parallel Scavenge追求CPU吞吐量,能够在较短时间内完成指定任务,适合没有交互的后台计算。ParNew追求降低用户停顿时间,适合交互式应用。
吞吐量=运行代码时间/(运行代码时间+垃圾收集时间)
追求高吞吐量可以通过减少GC执行实际工作的时间,仅仅偶尔运行GC意味着每当GC运行时将有许多工作要做,因为在此期间积累在堆中的对象数量很高。单个GC需要花更多的时间来完成,从而导致更高的暂停时间。而考虑到低暂停时间,最好频繁运行GC以便更快速完成,反过来又导致吞吐量下降。
通过参数 -XX:GCTimeRadio设置垃圾回收时间占总CPU时间的百分比。
通过参数 -XX:MaxGCPauseMillis设置垃圾处理过程最久停顿时间。
通过 -XX:+UseAdaptiveSizePolicy开启自适应策略。只要设置好堆的大小和 MaxGCPauseMillis或GCTimeRadio,收集器会自动调整新生代的大小、Eden和Survivor的比例、对象进入老年代的年龄,以最大程度上接近我们设置的MaxGCPauseMillis或GCTimeRadio。
只对老年代进行垃圾收集。
SerialOld垃圾收集器(单线程)
Serial Old收集器是Serial的老年代版本,都是单线程收集器,只启用一条GC线程。唯一区别就是Serial Old工作在老年代使用"标记-整理"算法,Serial工作在新生代使用"复制"算法。
ParallelOld垃圾收集器(多线程)
Parallel Old收集器是Parallel Scavenge的老年代版本,追求CPU吞吐量。
CMS(Concurrent Mark Sweep,并发标记清除)收集器是以获取最短回收停顿时间为目标的收集器(追求低停顿),它在垃圾收集时使得用户线程和GC线程并发执行,所以在垃圾收集过程中用户不会感到明显的卡顿。
初始标记:停止一切用户线程(Stop The World),仅使用一条初始标记线程对所有与GC Roots直接关联的对象进行标记。
并发标记:使用多条标记线程,与用户线程并发执行。此过程进行可达性分析,标记出所有废弃对象。速度很慢。
重新标记:停止一切用户线程(Stop The World),使用多条标记线程并发执行,将刚才并发标记过程中新出现的废弃对象标记出来。
并发清除:只使用一条GC线程,与用户线程并发执行,清除刚才标记的对象。这个过程非常耗时。
并发标记与并发清除过程耗时最长,且可以与用户线程一起工作,所以总体上说,CMS收集器的内存回收过程是与用户线程并发执行的。
CMS的缺点:吞吐量低、无法处理浮动垃圾、使用"标记-清除"算法产生碎片空间,导致频繁Full GC。对于产生碎片空间的问题,可通过开启 -XX:+UseCMSCompactAtFullCollection,在每次 Full GC完成后都会进行一次内存压缩整理,将零散在各处的对象整理到一块。设置参数 -XX:CMSFullGCsBeforeCompaction 告诉CMS,经过了N次Full GC之后再进行一次内存整理。
G1是一款面向服务端应用的垃圾收集器,它没有新生代和老年代的概念,而是将堆划分为一块块独立的Region。当要进行垃圾收集时,首先评估每个Region中垃圾的数量,每次都从垃圾回收价值最大的Region开始回收,可以获得最大的回收效率。从整体上看G1是基于“标记-整理”算法实现的收集器,从局部上看是基于“复制”算法实现的,这意味着运行期间不会产生内存空间碎片。
这里有个问题
一个对象和它内部所引用的对象可能不在同一个Region中,那么当垃圾回收时,是否需要扫描整个堆内存才能完整地进行一次可达性分析?
每个Region都有一个Remembered Set,用于记录本区域中所有对象引用的对象所在的区域,进行可达性分析时,只要在GC Roots中再加上Remembered Set即可防止对整个堆内存进行遍历。如果不计算维护Remembered Set的操作,G1收集器的工作过程分为以下几个步骤:
初始标记:停止一切用户线程(Stop The World),仅使用一条初始标记线程对所有与GC Roots直接关联的对象进行标记。
并发标记:使用一条标记线程与用户线程并发执行。此过程进行可达性分析,速度很慢。
最终标记:停止一切用户线程(Stop The World),使用多条标记线程并发执行。
筛选回收:回收废弃对象,此时也要停止一切用户线程(Stop The World),并使用多条筛选回收线程并发执行。
对整个新生代和部分老年代进行垃圾收集。
收集整个Java堆和方法区。
对象的内存分配,就是在堆上分配(也可能经过JIT编译后被拆散为标量类型并间接在栈上分配),对象主要分配在新生代的Eden区上,少数情况下可能直接分配在老年代,分配规则不固定,取决于当前使用的垃圾收集器组合以及相关的参数配置。以下列举几条普遍的内存分配规则
在Eden分配
大多数情况下,对象在新生代Eden区中分配,当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC。
MinorGC vs MajorGC/Full GC
MinorGC:回收新生代(包括Eden和Survivor区域),Java对象大多都具备朝生夕灭的特性,所以MinorGC非常频繁,一般回收速度也比较快。
MajorGC/FullGC:回收老年代,MajorGC的速度一般会比MinorGC慢10倍以上。
在JVM规范中,Major GC和Full GC都没有一个正式的定义,所以有人也简单地认为Major GC清理老年代,而Full GC清理整个内存堆。
大对象直接进入老年代
大对象是指需要大量连续内存空间的Java对象,如很长的字符串或数据。一个大对象能够存入 Eden区的概率比较小,发生分配担保的概率比较大,而分配担保需要涉及大量的复制,就会造成效率低下。虚拟机提供了一个 -XX:PretenureSizeThreshold 参数,令大于这个设置值的对象直接在老年代分配,这样做的目的是避免在 Eden 区及两个 Survivor 区之间发生大量的内存复制。(还记得吗,新生代采用复制算法回收垃圾)
长期存活的对象将进入老年代
JVM给每个对象定义了一个对象年龄计数器。当新生代发生一次Minor GC后,存活下来的对象年龄+1,当年龄超过一定值时,就将超过该值的所有对象转移到老年代中去。使用 -XXMaxTenuringThreshold
设置新生代的最大年龄,只要超过该参数的新生代对象都会被转移到老年代中去。
动态对象年龄判定
如果当前新生代的Survivor中,相同年龄所有对象大小的总和大于Survivor空间的一半,年龄 >= 该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold
中要求的年龄。
空间分配担保
JDK 6 Update 24之前的规则是这样的:在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间, 如果这个条件成立,Minor GC可以确保是安全的,如果不成立,则虚拟机会查看HandlePromotionFailure
值是否设置为允许担保失败, 如果是,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小, 如果大于,将尝试进行一次Minor GC,尽管这次Minor GC是有风险的, 如果小于,或者HandlePromotionFailure
设置不允许冒险,那此时也要改为进行一次Full GC。
JDK 6 Update 24 之后的规则变为:只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小,就会进行Minor GC,否则将进行Full GC。通过清除老年代中的废弃数据来扩大老年代空闲空间,以便给新生代作担保。这个过程就是分配担保。
可能会触发JVM进行Full GC的情况:
System.gc()
方法的调用此方法的调用是建议JVM进行Full GC,注意这只是建议而非一定,但在很多情况下它会触发Full GC,从而增加Full GC的频率。通常情况下我们只需要让虚拟机自己去管理内存即可,可以通过 -XX:+ DisableExplicitGC来禁止调用System.gc()
。
老年代空间不足 老年代空间不足会触发Full GC操作,若进行该操作后空间依然不足,则会抛出如下错误:java.lang.OutOfMemoryError: Java heap space
永久代空间不足 JVM规范中运行时数据区域中的方法区,在 HotSpot 虚拟机中也称为永久代(Permanet Generation),存放一些类信息、常量、静态变量等数据,当系统要加载的类、反射的类和调用的方法较多时,永久代可能会被占满,会触发 Full GC。如果经过 Full GC 仍然回收不了,那么 JVM 会抛出如下错误信息:java.lang.OutOfMemoryError: PermGen space
CMS GC时出现promotion failed
和concurrent mode failure
promotion failed,就是上文所说的担保失败,而concurrent mode failure是在执行CMS GC的过程中同时有对象要放入老年代,而此时老年代空间不足造成的。
统计得到的Minor GC晋升到旧生代的平均大小大于老年代的剩余空间。