美文网首页JVM学习记
JVM(八)-垃圾回收机制与垃圾收集器

JVM(八)-垃圾回收机制与垃圾收集器

作者: r09er | 来源:发表于2020-03-25 18:06 被阅读0次

    JVM垃圾回收(GC)模型

    • 垃圾判断算法
    • GC算法
    • 垃圾收集器的实现和选择

    垃圾判断算法

    引用计数法(Reference Couting)

    算法逻辑

    给对象添加一个引用计数器,当一个地方引用它,计数器+1,当引用失效,计数器-1.任何时刻计算器对象为0的对象就是不能再被使用的.

    算法弊端
    无法解决循环依赖问题.即A依赖于B,B也依赖于A.

    根搜索算法(GC Roots Tracing)

    HotSpot使用的也是根搜索算法判定对象是否存活

    算法逻辑
    通过一系列称为"GC Roots"的点作为起始,向下搜索,当一个对象到GC Roots没有任何引用链时,则认为此对象不可用

    GC Roots包括

    • stack中的引用
    • 方法区中的静态引用
    • Native方法的引用

    GC适用的内存区域

    方法区

    JVM规范表示这部分区域虚拟机可以不进行GC实现,这部分区域的垃圾回收效果比较一般.

    目前的商业JVM中都有实现方法区的GC,主要回收两部分内容:废弃常量与无用类

    类回收需要满足的条件
    • 该类所有的实例都已经被GC,JVM中不存在该Class的任何实例
    • 加载该类的ClassLoader已经被GC
    • 该类对应的.Class对象没有在任何地方被引用,不能再任何地方通过反射访问该类的方法.

    堆内存是GC的主要回收区域,在堆内存中,尤其是新生代,常规应用进行一次GC,一般多可以回收70~95%的空间,而方法区的效率远远低于此.

    GC算法

    标记-清除(mark-sweep)

    算法逻辑
    分为标记清除两阶段,首先标记需要回收的对象,然后进行回收.

    缺陷

    • 效率,标记和清除效率不高
    • 空间,标记清除后会产生大量不连续的内存碎片,导致后续对象分配中无法找到足够的内存而提前触发另一次GC.

    标记-整理(mark-compact)

    算法逻辑
    标记过程和其他算法基本一致,但后续步骤不进行直接清理,而是令所有存活的对象一端移动,然后直接清理掉这段边界以外的内存

    优点
    不会产生内存碎片

    缺陷
    比标记清除算法会耗费更多的时间进行整理压缩

    执行流程

    1.标记所有存活对象


    标记整理算法1

    2.压缩整理存活对象

    标记整理算法2

    复制算法(Copying)

    算法逻辑
    将可用内存划分为两块,每次只用其中一块,当半区的内存用完了,将还存活的对象赋值到另一块,然后把原来的整块内存一次性清理掉.

    适用场景
    适合生命周期短的对象,因为每次GC都能回收大部分的对象.

    优点
    因为对象只在其中一块内存区域中,当GC触发后,也是整个内存区域进行回收,不会产生碎片.

    缺陷
    需要两份大小一致的内存区域,对空间利用率不高

    HotSpot虚拟机默认的survivor 的from和to区就是采用该种算法.并且与eden的比例是8:1.即有1份用于复制转移用的空间.

    执行流程

    1.从GC Roots出发,找到对象的引用链


    复制算法1

    2.将存活的对象全部复制到To

    复制算法

    3.将from区整个区域进行垃圾回收

    复制算法3

    分代算法(Generational GC)

    目前商业虚拟机的垃圾收集都是采用"分代收集"算法,根据对象不同的存活周期将内存进行逻辑划分

    一般会把Java堆(Heap)内存划分为新生代(young generation)老年代(Old generation),这样就可以根据不同代的特点选用最适合的收集算法.

    heap内存分代

    年轻代(Young Generation)

    • 新生成的对象(小对象)会放在年轻代.年轻代使用复制算法进行GC
    • 年轻代又分为三个逻辑区域,eden,survivor from,survivor to.

    经历多次GC后,存活的对象会在Survivor From和Survivor To之间来回存放,这里有一个前提就是这两个空间有足够的大小来存放这些数据,在GC算法中,会计算每个对象年龄的大小,如果达到某个年龄后发现总大小已经大于Survivor to空间的50%,那么这时候就需要调整阈值,将对象尽快晋升到老年代,防止Survivor空间不足.

    老年代(Old Generation)

    • 老年代存放经历多次GC还存活,或者大对象
    • 老年代一般采用标记-清理或者标记-压缩算法进行GC
    • 有多种垃圾收集器可以选择.每种垃圾收集器可以看作一个GC算法的具体实现.

    HotSpot中的GC算法

    HotSpot虚拟机中的垃圾回收类型

    HotSpot垃圾收集器 HotSpot垃圾收集类型
    • 年轻代收集

      • Serial,STW的单线程复制收集器
      • ParNew,STW的多线程复制收集器
      • Parallel Scanvenge ,STW的复制多线程收集器
    • 老年代收集

      • Serial Old,STW的标记-清除-整理单线程收集器
      • CMS,并发且短暂停顿的收集器
      • Parallel Old,多线程的压缩收集器
    • G1收集器

      • G1是用于大型堆的垃圾优先收集器,并提供可靠的GC短暂停
      • 在JDK9中,G1收集器被用作默认的收集器

    注意在Java9中,CMS收集器将会被废弃

    HotSpot中提供了多种的垃圾收集器,需要根据具体应用的需求采用不同的收集器.

    没有万能的垃圾收集器,每种收集器都有适用场景

    垃圾收集器的并行(Parallel)并发(Concurrent)

    并行(Parallel):指多个收集器的线程同时工作,但是用户线程处于等待状态.
    并发(Concurrent):指收集器在工作的同时,允许用户线程工作

    并发并不代表解决了STW的问题,在关键步骤依然需要停顿.比如在收集器标记垃圾时候需要停顿,但是在清除阶段,用户线程和GC线程可以并发执行.
    如果在标记过程中用户线程可以工作,那么就会不断有新的对象产生,那么标记的对象就会不准确.

    Serial收集器

    单线程收集器,收集时会暂停所有工作线程,Stop the world,简称STW,使用复制算法,虚拟机运行在Client模式的时候默认的新生代收集器

    • 最早的收集器,单线程进行GC
    • young/old gen都可以使用
    • 在新生代,采用复制算法,在老年代使用标记-清理-压缩(mark-sweep-compact)算法.

    ParNew收集器

    ParNew收集器就是Serial收集器的多线程版本,除了使用多个收集线程外,其余行为与Serial一样.

    虚拟机运行在Server模式的新生代收集器

    可以通过-XX:ParallelGCThreads控制GC线程数量

    Parallel Scavenge收集器

    Parallel Scavenge收集器也是多线程收集器,也使用复制算法,但是这种收集器的目的是将吞吐量最大化(即GC时间占总运行时间最小)为目标实现的,允许较长时间的STW换取总吞吐量最大.

    Serial Old收集器

    Serial Old是老年代的单线程收集器,使用标记-整理-压缩算法

    Parallel Old收集器

    老年代的吞吐量优先收集器,使用多线程和标记-压缩算法,JVM1.6开始提供.

    使用Parallel Scavenge +Parallel Old = 高吞吐量,但GC停顿可能不理想

    配置HotSpot中的垃圾收集器

    在上述的各种GC收集器中,其中在真正使用都是两两配对使用的.所以就会有JVM参数进行GC收集器的配置.

    标记-清除-整理收集器(Mark-Sweep-Compact Collector)

    • -XX:+UseSerialGC: Serial young+Serial Old

    • -XX:+UseParNewGC,Parallel young+ Serial Old

    • 老年代垃圾收集时发生STW

    • 标记阶段会标记所有存活对象

    • 清除阶段会扫描整个被标记的堆heap

    • 整理阶段会将存活对象推向堆的起始位置.

    并行收集器(Parallel Collector)

    • -XX:+UseParallelGC:Parallel Scavenage + Parallel Old
    • 也称为吞吐量收集器
    • 收集时发生STW
    • Server模式下,是JDK9之前默认的收集器
    • 在多核环境下进行多线程的并行收集

    并发标记清除收集器(Concurrent Mark Sweep Collector)

    • -XX:+UseConcMarkSweepGC:ParNewGC +CMS
    • 低延迟,尽量并发
    • 不进行堆内存整理-会导致碎片化
    • 空闲列表与未分配空间是关联的
    • 与指针分配相比,开销更大
    • 对年轻代回收有额外的开销
    • 要求堆内存有更大的空间和浮动收集
    • 在JDK9中被弃用

    G1收集器

    • Server模式的收集器,目标是运行在多核大内存的机器中
    • GC时间极短,并且拥有高吞量
    • 更好的GC工程学
    • 停顿时间短,并且不会导致碎片化
    • 并行且并发地进行垃圾收集
    • 是整理型的收集器(不会导致碎片化)
    • 从OracleJDK7u4开始支持
    • 是JDK9中默认的GC收集器

    GC时机

    在分代模型的基础上,GC从时机上分为两种.
    Minor GCMajor GC(Full GC)

    Minor GC

    触发时机: 新对象生成时,Eden空间满了.
    理论上Eden区大多数对象会在MinorGC过程中被回收,复制算法执行效率会很高,所以MinorGC时间比较短

    Major GC

    MajorGC会对整个JVM进行整理,包括heap,Metaspace.
    进行MajorGC会造成STW(Stop the world).将所有执行线程停止,然后进行垃圾回收,所以需要尽量将低MajorGC的次数和频率

    FullGC触发时机:

    • 1.老年代满了
    • 2.System.gc()
    • 3.heap dump
    • 4.MetaSpace满了

    关于MetaSpace到达设置值触发FullGC的回答https://stackoverflow.com/questions/53101801/java-metaspace-full-gc

    内存分配

    1.堆上分配

    大多数情况下在eden上分配,偶尔会直接在old上分配
    细节取决于GC实现

    2.栈上分配

    原始类型的局部变量

    内存回收

    GC要做的就是将已经消亡的对象所占用的内存回收掉

    • HotSpot认为没有引用的对象就是消亡的
    • HotSpot将引用分为四大类
      • 强引用(Strong Reference)
      • 软引用(Soft Reference)
      • 弱引用(Weak Reference)
      • 虚引用(Phantom Reference)

    强引用

    正常通过new构造的对象都是强引用

    软引用

    FullGC,内存不够时一定会被GC,长期不用也会被GC

    弱引用

    FullGC,一定会被GC,被标记为消亡对象时,会在ReferenceQueue中通知

    需引用

    本来就没有引用,从heap中释放时会通知

    内存泄漏的经典原因

    • 对象定义在错误范围
    • 异常处理不当
    异常导致资源没有正常关闭

    推荐使用try-resources处理资源关闭

    • 集合数据管理不当
    集合数据管理

    关于GC STW的详细内容

    枚举根节点

    当执行提供停顿下来后,并不需要一个不漏地检查完所有执行上下文和全局的引用位置,虚拟机应当时有办法直接得知哪些地方存放着对象引用.在HotSpot的实现中,是使用一组称为OopMap的数据结构来达到这个目的.

    安全点

    在OopMap的协助下,HotSpot可以快速且准确地完成GC Roots枚举,但是导致OopMap内容变化的指令非常多,如果每一条指令都生成对应的OopMap,会需要大量的额外空间,这样GC的空间成本会变得非常高.

    实际上,HotSpot没有为每条指令都生成OopMap,而是再特定位置记录信息,这些位置称为安全点(SafePoint),即程序执行时并非在所有订房都能停下来开始GC,只有达到安全点时才能暂停.

    • 对于安全点,另一个需要考虑的问题就是当GC发生时,让所有线程都在安全点停顿下来.引出抢占式中断(Preemptive Suspension)主动式中断(Voluntary Suspension)
    抢占式中断(Preemptive Suspension)

    不需要线程的执行代码主动配合,在GC发生时,会把所有线程都中断,如果有线程中断的地方不在安全点上,就恢复线程,让它继续执行到安全点上

    主动式中断(Voluntary Suspension)

    当GC需要中断线程时候,不直接对线程操作,仅仅设置一个标记,各个线程执行时主动轮询这个标记,发现中断标记为真时就中断挂起,轮询标记的地方与安全点是重合的,另外加载创建对象需要分配内存的地方

    几乎没有虚拟机会采用抢占式中断来暂停线程来响应GC

    安全区域

    由于线程并不是一直运行的,当出现线程Blocked状态时候,并不能响应JVM的中断请求,所以需要安全区域(Safe Regin).

    在线程执行到安全区域时,会标识自己进入了安全区域,在这段时间内JVM发起GC,就不需要管安全点的线程.当线程离开安全区域时,会检查GC Roots是否完成,如果完成就继续执行,如果没有就等待直到收到可以离开的信号.

    参考资料

    圣思园-深入理解JVM

    深入理解Java虚拟机

    Oracle-jvm-lesson

    相关文章

      网友评论

        本文标题:JVM(八)-垃圾回收机制与垃圾收集器

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