面试的时候提及了各个GC不同的选择,虽然以前了解过,但是还没有真正地总结过。小记一下。大概内容分以下一些步骤:
从一系列的被称为 GC Root
对象开始,通过对象引用向下搜索,遍历引用链。不被遍历到的对象会被标记为 游离对象
,会在清理的时候被清理掉。 GC Root
对象包括以下:
PS: 可达性分析时,不可达的对象非删除不可?
finalize
,假如在 finalize
方法中自我救赎,重新建立引用,则对象不会被删除。但 finalize
方法虚拟机只会对同一对象执行一次,是不可靠的方法。大部分程序都不会选择重写这个方法。 方法区的回收效率是很低的,因为里面保存的大量都是类对象和类的静态变量,这些很少会被回收。一般回收的对象只有两种:废弃的常量和没用的类
指,假设常量存在在一个 "abc" 字符串,但整个 JVM 中都没有引用这个字面常量,内存回收就会回收这个常量对象。
PS: 大量使用反射、动态代理、CGLib、ASM 等字节码框架、动态生成 JPS、OSGi等自定义 ClassLoader 一般需要具备类卸载功能,保证永久代不溢出。
最基础的收集算法。
利用双倍空间,消除上述的问题。适合朝生夕死的对象
根据对象存活的特点选择不同的算法。新生代采用复制,老年代使用标记清除、或标记整理算法。
由于 GC 过程中需要考虑一致性的问题,防止因为对象关系的变动,在进行可达性分析时存在漏判或误判,JVM 在 GC 进行的过程中暂停 Java 所有的执行线程。称为 Stop the world。
如果要触发一次 GC ,那么 JVM 中所有的 Java 线程都必须到达 GC safe point。JVM 只会在特点的位置放置 safe point,譬如:
safe point 还需要考虑一个问题,如何让线程在 safe point 的位置挂起?在设计上有两种方案:抢占式中断和主动式中断
假如线程一直得不到 cpu 资源,由于饥饿无法到达safe point,改如何处理?
PS:整理思路下来,Stop the world其实不一定真的是需要严格挂起所有的用户线程,有点用户线程假如在 安全区域
里,可能还能活动一下。当然,鸡蛋里挑骨头是没什么必要的。
没有任何一种收集器是放之皆准的。
最基本,最简答的收集器。单线程,主要工作原理,在 JVM 需要 GC 时,暂停所有的工作线程,对新生代进行 复制
GC 清除,对老年代进行 标记-整理
清除。
Serial 收集器的多线程版本,暂时没有很创新的地方,但是可以与 CMS 收集器共同协作,因此被青睐。
这个收集器的目标,是达到一个可控制的吞吐量。 所谓吞吐量,就是 CPU 运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码的时间/(运行用户代码时间 + 垃圾收集器时间),eg: 虚拟机一共运行100分钟,其中垃圾收集划掉1分钟,那吞吐量就是99%
控制吞吐量大小的两个参数:
Parallel Scavenge 收集器的老年代版本。Parallel Scavenge 收集器是一个优秀的年轻代收集器,但只能与Serial Old合作,于是有了 Parallel Old 与其对接。其组合就是一个吞吐量优先的配置组合,适合一些 CPU 资源比较敏感的应用。
Concurrent Mark Sweep 收集器是一种以获取最短回收停顿时间为目标的收集器。关注服务端的响应速度,希望系统停顿时间短,CMS 收集器会非常符合这类应用。过程分4个步骤
以下有几个特点:
缺点:
-XX:CMSInitiatingOccupancyFraction=70
这个参数是用于控制比率的,老年代的空间达到70%时,激活CMS GC。但假如设置太高,则会导致 Concurrent Mode Failure
,GC 会采用后备方案,使用 Serial Old进行。 PS: Full GC 不等于 CMS GC。
G1 收集器是一个比较先进的收集器。下面详细介绍一下。
G1 会把一整块的堆空间,划分为固定内存的 region,大小从1-32Mb不等。
region 会被分为 Eden、Survivor和old,这只是一个标签。对 region 的回收是并行的,其他线程照常工作。