JVM学习笔记与调优实战(二):GC

标签: JVM


1、什么是可回收对象(垃圾)?


画图演示

GC垃圾


  • 引用:

    • 强引用:强引用指的是子啊程序代码中普通存在的,类似Object obj = new Object()这类的引用,只要强引用还在,垃圾收集器则不会回收掉被引用的对象.

    • 软引用:软引用用于描述一些还有用但并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常,在jdk1.2之后,提供了SoftReference类来实现软引用.

    • 弱引用:弱引用也是用于描述非必须对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象,在jdk1.2之后,提供了WeakReference类来实现弱引用.

    • 虚引用:也称为幽灵引用、幻影引用,是最弱的一种引用关系,一个对象是否有虚引用的存在,不会对其生成时间构成影响,无法通过虚引用来引用对象。为一个对象设置虚引用的目的是能在这个对象被收集器回收时收到一个系统通知,jdk1.2提供PhantomReference类来实现虚引用.



2、GC是如何确定垃圾的?

  • 引用计数法
    • 算法思路:给对象添加一个引用计数器,每当有一个地方引用它时,计数器值加1;当引用失效时,计数器值减1;任何时刻计数器为0的对象则是不可用的。

    • 引用计数算法(Reference Counting)的实现简单,判定效率也很高,但是无法解决循环引用问题

  • 可达性分析
    • 从roots对象计算可以达到的对象

    • 可作为GC Roots的对象包括:

      • 虚拟机栈(栈帧中的本地变量表)中引用的对象
      • 方法区中类静态属性引用的对象
      • 方法区中常量引用的对象
      • 本地方法栈中JNI引用的对象

    • 算法思路:以GC Roots 对象为起始点,从这些节点开始向下搜索(深度搜索),搜索所走过的路劲成为引用链(Reference Chain),当一个对象到GCRoots不存在引用链(不可达)时,则证明此对象是不可用,即判定为可回收对象。逻辑图如下:

可达性分析



一种可能的物理内存图:

可达性分析内存图

3、GC算法

  • Mark-Sweep标记清除:
    • 算法分为标记和清除两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象,标记过程通过可达性分析,将不可达的对象进行标记判定。

    • 不足之处:

      • 效率问题,标记和清除两个过程效率都不高
      • 空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致后续需要分配较大内存的对象时,无法找到足够的连续内存,而不得不提前触发一次FGC


        标记清除


  • Copying复制:
    • 将内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把使用过的内存空间一次清理掉。常用于新生代 survivor区的from/to的复制

    • 优点:在内存上进行复制效率高,不存在内存碎片化问题

    • 缺点:内存空间利用率低,算法代价高,因此实际分给新生代中的survivor区内存较小,与Eden区比例约为8:1:1


      复制算法


  • Mark-Compact标记压缩:
    • 标记过程仍然与标记-清除算法一样,采用可达性分析标记判定,然后让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存。

    • 该算法效率略低于复制算法,但内存空间利用率高,常用于老年代GC

标记压缩算法


  • 分代收集算法:当前商业虚拟机的垃圾收集都采用分代收集(GenerationalCollection)算法,,该算法根据对象存活周期的不同将堆内存划分为几块:新生代、老年代,然后根据各个年代的特点采用最适当的GC算法:

    • 在新生代中,每次GC时都发现有大量对象死去,只有少量存活,则选用复制算法,只需要付出少量存活对象的复制成本即可完成GC

    • 在老年代中,对象存活率高、,没有额外空间对它进行分配担保,则需要使用标记-清除算法或者标记-压缩算法(默认使用)进行GC


4、垃圾收集器:翻译自官方文档

  • 串行收集器 Serial Collector:
    • 串行收集器使用单个线程执行所有垃圾收集工作,这使得它相对高效,因为线程之间没有通信开销。它最适合于单处理器机器,因为它不能利用多处理器硬件,尽管对于具有小数据集(高达大约100 MB)的应用程序,它可能对多处理器很有用。串行收集器在某些硬件和操作系统配置中默认选中,或者可以使用该选项明确启用-XX:+UseSerialGC。

    • XX:+UseSerialGC

    • 单线程

  • 并行收集器 Paraller Collector:
    • 官方文档(翻译):并行收集器(也称为吞吐量收集器)并行执行次要收集,这可以显着减少垃圾收集开销。它适用于在多处理器或多线程硬件上运行的中型到大型数据集的应用程序。并行收集器在某些硬件和操作系统配置上默认选中,或者可以使用该选项明确启用-XX:+UseParallelGC。

    • 并行压缩是一个使并行采集器能够并行执行主要采集的功能。如果没有并行压缩,主要集合将使用单个线程执行,这可能会极大地限制可伸缩性。如果-XX:+UseParallelGC指定了选项,则默认启用并行压缩。关闭它的选项是-XX:-UseParallelOldGC。

    • 并发量大,每次GC时,JVM需要停顿

  • 并发收集器
    • CMS Collector: 此收集器适用于希望缩短垃圾收集暂停时间并能够与垃圾收集共享处理器资源的应用程序。

      • 停顿时间短
    • G1: 这种服务器式垃圾收集器适用于内存较大的多处理器机器。它以高概率满足垃圾收集暂停时间目标,同时实现高吞吐量。

      • 停顿短,同时并发大

  • 并发开销:
    • 大多数并发收集器交换处理器资源(否则可用于应用程序)以缩短主要收集暂停时间。最明显的开销是在收集的并发部分期间使用一个或多个处理器。在N处理器系统上,并发部分集合将使用可用处理器的K / N,其中1 <= K <= ceiling { N / 4}。(注意K上的精确选择和边界)除了在并行阶段使用处理器之外,还会产生额外的开销以实现并发。因此,虽然垃圾收集暂停通常比并发收集器短得多,但应用程序吞吐量也往往略低于其他收集器。

    • 在具有多个处理核心的计算机上,处理器可用于集合并发部分中的应用程序线程,因此并发垃圾收集器线程不会“暂停”应用程序。这通常会导致更短的暂停,但是应用程序可用的处理器资源也较少,应该会出现一些减速,特别是在应用程序最大限度地使用所有处理内核的情况下。随着N的增加,由于并发垃圾收集导致的处理器资源减少变得更小,同时收集的收益也增加。的部分并行模故障在并发标记扫描(CMS)集电极讨论了这样的缩放潜在限制。

    • 由于至少有一个处理器用于并发阶段的垃圾收集,因此并发收集器通常不会为单处理器(单核)机器提供任何好处。但是,对于CMS(不是G1),可以使用单独的模式,可以在只有一个或两个处理器的系统上实现低暂停; 看到增量模式在并发标记扫描(CMS)收集器的详细信息。此功能在Java SE 8中不推荐使用,并可能在以后的主要版本中删除。

  • 选择收集器:除非应用程序具有相当严格的暂停时间要求,否则请先运行您的应用程序并允许VM选择收集器。如有必要,请调整堆大小以提高性能。如果性能仍不能达到您的目标,请使用以下指南作为选择收集器的起点。
    • 如果应用程序有一个小数据集(最多大约100MB),那么用选项选择串行收集器-XX:+UseSerialGC。

    • 如果应用程序将在单个处理器上运行,并且没有暂停时间要求,则让VM选择收集器,或者使用该选项选择串行收集器-XX:+UseSerialGC。

    • 如果(a)峰值应用程序性能是第一优先级并且(b)没有暂停时间要求或暂停1秒或更长时间是可接受的,则让VM选择收集器,或者选择并行收集器-XX:+UseParallelGC。

    • 如果响应时间比整体吞吐量更重要,并且垃圾收集暂停时间必须短于大约1秒,那么使用-XX:+UseConcMarkSweepGC或选择并发收集器-XX:+UseG1GC。

    • 如果推荐的收集器无法达到所需的性能,请首先尝试调整堆和代的大小以达到所需的目标。如果性能仍然不足,请尝试使用其他收集器:使用并发收集器来减少暂停时间,并使用并行收集器来提高多处理器硬件的整体吞吐量。


参考资料:
1.《深入理解Java虚拟机》
2.官方文档

你可能感兴趣的:(JVM学习笔记与调优实战(二):GC)