美文网首页
Java程序进阶课程学习(四)

Java程序进阶课程学习(四)

作者: MikeShine | 来源:发表于2020-02-06 11:06 被阅读0次

    写在前面

    在上一部分的学习中,我们对 JVM 的基础概念、JVM运行时内存、类加载机制有了基本的了解。
    下面我们开始对于 JVM 的重点----GC,来进行学习。


    6.5 GC中,如何判断对象存活、对象引用

    什么是垃圾

    很好理解,当一个对象没有引用指向他的时候,他就是垃圾。

    判断对象是否存活的算法

    为了判断一个对象是否存活,有多种算法。

    引用计数算法

    顾名思义,对一个对象,我们统计其引用的个数,进行计数。当引用次数为0的时候,即可以判断他变成垃圾了。
    引用计数器算法很直观,也很简单。但是并没有在 Java 中使用,因为其不能解决 对象间循环引用 的问题。

    //  如果使用引用计数算法,GC无法回收循环引用的情况
    ObjA.instance = ObjB
    ObjB.instance = ObjA
    
    可达性分析算法(根搜索算法)

    既然引用搜索算法无法满足要求,那么 java 就采用了另外一种算法----可达性分析算法。
    可达性分析算法中,从根节点GC ROOT 出发,向下搜索引用链,对象如果不可被搜索到(不可达),就是垃圾。
    那么什么样的对象可以看做是 GC ROOT 节点呢?

    • 栈中引用的对象(局部对象)
    • 方法区中类静态属性引用的对象
    • 方法区中的常量引用的对象
    • 本地方法栈引用的对象

    总结一下,栈和方法区引用的对象。
    两个栈(JVM栈和本地方法栈)引用的对象,方法区中 类静态属性、常量 引用的对象

    再谈对象引用

    这里判断对象是否存活,都是跟 “引用” 息息相关的。而传统意义上的引用,就只有被引用,和没有被引用两种状态。
    而很多时候我们需要描述一些,内存空间足够则保留,内存不够则抛弃的对象,则需要多种的引用形式。
    Java中同样给出了多种引用形式,下面列出来的引用依次变弱。

    • 强引用
      就是传统意义上理解的引用。 Object obj = new Object() 这种引用。只要强引用存在,就一定不会回收
    • 软引用
      软引用用来描述 有用,非必须的元素
      GC第一次收集之后,软引用对象存在。但是如果内存还是不够(要内存溢出),则会触发GC第二次回收,此时,软引用对象就会被回收。

    这里给一个应用场景来理解一下:
    我们要实现一个缓存的功能,当内存足够的时候,通过一个软引用对象来取值,而不是直接从繁忙的真实数据来源来取值。内存是在不够了,这个软引用对象被回收,再从数据来源来直接取值。

    • 弱引用
      弱引用描述 非必须的对象
      弱引用的对象,在下一次垃圾GC时候被回收。弱引用主要用来监控对象是否已经被GC标记为即将回收(下一次GC)的垃圾。弱引用的 isEnQueued() 方法就可以返回该对象是否要被下次回收。
    • 虚引用
      唯一目的,就是在对象被GC回收时候,收到一个系统通知。

    后面三者都有自己对应的实现类。 SoftReference/ WeakReference/ PhantomReference 类。


    6.6 分代垃圾回收

    上面一小节我们了解了对于一个对象,JVM是如何判断其是否已经变成垃圾了。那么变成垃圾之后呢?当然是要 GC 了。
    目前的垃圾回收,核心都是分代垃圾回收机制。要理解分代回收机制。首先就要明白分代对于内存的划分。
    这一部分在下面一小节对于 分代收集算法中,有详细的解释。


    6.7 典型的垃圾收集算法

    在上面一小节“分代垃圾回收”的基本思想指导下,我们来学习一下目前典型的垃圾收集算法。

    mark-sweep标记清除算法

    mark-sweep算法是最简单直接的垃圾收集算法,分为两个阶段:标记 + 清除。即标记出需要被回收的对象,再清除对象所占用的空间。

    • 优点:实现简单。
    • 缺点:容易产生内存碎片。
      可能会导致大对象分配内存空间时,触发新一轮的GC。
    Copying复制算法

    将内存一分为二,只用一半。当这一半用完之后,将存活的对象放到另外一半,把这一半清除掉。

    这么理解吧,有点类似于缓存的思想。

    • 优点:没有内存碎片
    • 缺点:内存只有一半。如果存活对象多的话,效率很低
    Mark-Compact 标记整理算法

    跟 mark-sweep 很类似。先标记,再把存活对象移动到一端,清理掉端边界以外的内存。

    Generational Collection 分代收集算法

    分代收集算法,比上面的三种GC算法复杂一点。
    核心思想是:根据对象存活的生命周期,把内存进行划分。将heap堆分为老年代(Tenured Generation)和新生代(Young Generation)。老年代的特点是,每次GC时候,只有少量的对象需要被回收,新生代则是大量的对象。

    新生代采用 Copying 算法,因为要回收的对象很多,存活的对象不多。新生代 = 1较大Eden + 2较小Survivor。 一般只使用一个Eden和Survivor。回收时候,将 Eden 和 Survivor存活的对象,复制到另外一个 Survivor,再清空前者。
    那么基于对内存的划分,就可以在不同的内存区域,采用合适的GC算法。

    对于老年代,采用 Mark-Compact 算法。
    这里需要注意以下几个点:
    MinorGC: 清理年轻代。 Major GC:清理老年代。FullGC:清理整个堆空间。

    1. 对象优先在 Eden 分配:
      对象优先在 Eden 区域分配。当 Eden 没有足够空间,虚拟机进行一次 Minor GC
    2. 大对象直接进入老年代
      为了避免 Eden 区和两个 Survivor 区的频繁内存复制,大对象直接进入老年代。
    3. 长期存活的对象进入老年代
      对象再 Eden 中出生,经过一次 Minor GC 后仍存活,被 Survivor接纳,其年龄被设为1,在 Survivor区每经历一个 Minor GC,年龄++,年龄增长到一定值,会被放到老年代中。
    4. 动态对象年龄判定
      并不是一定要满足3中年龄达到阈值的情况,对象才可以进入老年代。
      同年龄对象 总和 超过 survivor 空间内存的一半,这些对象就会进入老年代。
    5. 空间分配担保
      首先,这里是有一个设置HandlePromotionFailure,来设定是否允许老年代来进行担保。
      其次,这里的担保,是指 在 MinorGC 时候,可能遇到一个 Survivor 空间不够用(年轻代中存活对象很多,极端就是都存活)。这时候就需要老年代借用空间给年轻代,这就是担保。
      而担保的前提是,老年代的空间也要够,但是实际上老年代并不知道这一次 MinorGC会存活多少对象,需要多少内存,所以就取一个之前每次均值作为比较对象,如果老年代现存对象比这个均值还要小,那么就需要执行 MajorGC 为年轻代腾出空间,做担保。

    跟内存空间联系起来:堆空间 heap 被分为年轻代和老年代。年轻代又分为 eden + 2 survivor

    上面说了新生代和老年代,都是在 heap 中的。实际上,在heap 之外还有一个永久代 Permanent Generation(就是方法区non-heap,在 HotSpot虚拟机中叫做永久代),存储 class 类、常量、方法描述等。对永久代的回收主要是回收 废弃常量、无用的类

    可以看到,永久代回收的东西已经不是对象了,而是类和常量。那自然也就不在 heap 中了。


    6.8 垃圾收集器

    前面一个小节我们介绍了GC算法,那么这一个小节会聚焦于GC的具体实现----垃圾收集器。
    首先是一张整体的图


    垃圾收集器全家桶
    Serial/
    • 单线程。
    • 新生代,Copying
    • 简单高效;但是会给用户带来停顿

    因为没有线程间交互的开销,所以可以简单高效的进行垃圾收集,对于 Client 模式下的虚拟机是首选。一般是使用 Serial(新生代垃圾收集) + Serial Old(老年代垃圾收集)

    ParNew
    • Serial 的多线程版本。(二者的实现绝大部分都相同)

    对于 Server 模式下的虚拟机是首选。因为除了 Serial之外,只有 ParNew 可以配合 CMS 收集器,实现并发收集(一边收集一边产生垃圾)。

    Parallel Scavenge (平行捡破烂)
    • 新生代,Copying。
    • 多线程(并行收集)

    目的:达到可控的吞吐量 ,高效利用CPU时间,尽快完成程序运算任务。
    吞吐量: \frac {cpu运行用户代码时间} {用户代码时间+GC时间}

    其他的收集器,基本都是关注于,缩短垃圾回收带来的停顿时间,而 Parallel Scavenge 则关注于吞吐量。
    停顿时间给交互带来了阻碍,而吞吐量则直接影响计算性能(CPU利用率)。所以 Parallel Scavenge 适合于用在后台运行,交互不多的任务

    Serial Old 收集器

    Serial 的老年代版本

    • 单线程
    • 老年代,Mark-Compact 标记整理算法。

    也是主要用于 Client 模式下的虚拟机。跟前面说的一样。

    Parallel Old
    • Parallel Scavenge 老年代版本,Mark-Compact 标记整理算法

    在注意吞吐量和CPU利用率时,Parallel Scavenge + Parallel Old。

    CMS(Concurrent Mark Sweep)

    目的:最短的垃圾收集停顿时间。所以一般用在网站服务器上,因为一般用户要求服务器获得最快的响应。

    • 并发
    • 老年代,标记清除(所有老年代中,唯一一个使用标记清除的)

    下面主要了解一下,其垃圾回收的过程。分为4步:初始标记、并发标记、重新标记、并发清除

    1. 初始标记(initial mark)
      标记GC ROOT能直接关联到的对象,速度很快。会停顿
    2. 并发标记(concurrent mark)
      GC ROOT Tracing 的过程,是最耗时的。(是并发执行的,用户线程不停止,所以叫并发标记)
    3. 重新标记(remark)
      标记步骤2期间,程序继续执行产生变动的对象的记录。会停顿较长时间
    4. 并发清除(concurrent sweep)

    并发标记、并发清除的 并发 是指垃圾处理进程和用户进程并发进行。初始标记、重新标记这里的并发,是指多个垃圾回收进程同时进行,而此时用户进程是暂停的。

    优点:并发收集、低停顿
    缺点:

    • CMS收集器 对 CPU资源非常敏感。其实只要涉及并发设计的程序,都会对CPU资源敏感。在CPU数量较少的时候,整体性能被拖慢很多。
    • CMS收集器 无法处理浮动垃圾。在并发清除时候,用户线程还是在运行,这时候产生的垃圾不会在本次垃圾回收被处理,称为浮动垃圾。
    • CMS 空间碎片。由于采用 标记清除算法,会产生内存碎片,引发 新的垃圾收集。
    • Concurrent Mode Failure 采用 SerialOld 作为老年代备用。
    G1(Garbage First)

    面向 服务端 应用的。标记整理算法。

    G1 收集器将堆内存划分为一个个Region。同时维护了各个 Region垃圾堆积价值列表,优先回收价值大,代价小的 Region。
    同时,为了避免全堆扫描,每一个 Region 都有一个对应的 Remembered Set。这个 Remembered Set 记录引用对象的信息,从而只要通过 Remembered Set 就可以找到相关对象,不用全堆扫描也不会遗漏。

    • 并行与并发:利用多cpu缩短 stop-the-world停顿时间
    • 分代收集:G1收集器同时运行于 新生代和老年代
    • 空间整合:没有内存碎片
    • 可预测的停顿:由于优先回收机制的存在,可以预测停顿时间。

    垃圾回收的过程总结为:

    1. 初始标记
      跟 CMS一样,先标记 GC ROOT 直接相关的对象。
    2. 并发标记
      GC ROOT Tracing过程,并发执行。
    3. 最终标记
      跟 CMS 一样
    4. 筛选回收
      根据维护的回收价值列表,来优先回收。

    可以看出,跟CMS来比,除了最后一步,都一样。

    总结一下:

    1. 串行、并行、并发:
    • 串行:Serial & Serial Old
    • 并行:ParNew & Parallel Scavenge & Para Old
    • 并发:CMS & G1
    1. 吞吐量优先和停顿时间优先
    • 吞吐量优先:Para Scavenge & Para Old
    • 停顿时间优先:CMS

    client 模式: Serial + SerialOld
    server 模式:ParNew + CMS
    低交互模式:Para Scavenge + ParaOld

    更加详细的垃圾回收器的整理

    相关文章

      网友评论

          本文标题:Java程序进阶课程学习(四)

          本文链接:https://www.haomeiwen.com/subject/dppyxhtx.html