美文网首页
2、自动内存管理:垃圾回收与内存分配

2、自动内存管理:垃圾回收与内存分配

作者: lois想当大佬 | 来源:发表于2019-12-14 22:11 被阅读0次

    一、垃圾回收解决3个问题

    1、哪些内存需要被回收?
    • 程序计数器、虚拟机栈、本地方法栈这3个区域随线程而生,随线程而灭,方法结束或线程结束时,内存自然就跟随回收了,因此无须过多考虑回收问题。
    • 堆和方法区内存是动态分配的,垃圾收集器所关注的是这部分内存。但是方法区回收性价比低,虚拟机规范不要求对其进行回收,因此,垃圾收集器最终只需关注java堆的回收。
    2、什么时候回收?

    判断对象已死则回收,两种对象存活判定方法:

    • 引用计数算法:给对象添加一个引用计数器,每增加一个引用则计数值加1;当引用失效则计数器减1;当引用计数器为0,则对象不可用。该方法无法解决循环引用问题。
    • 可达性分析算法:GC Roots对象作为起点,向下搜索,搜索路径称为引用链,当一个对象到GC Roots没有任何引用链相连,则此对象不可用。
      GC Roots对象:
      A、虚拟机栈和本地方法栈引用的对象。
      B、方法区常量或类静态属性所引用的对象。
    3、如何回收?
    垃圾收集器
    image.png

    1、新生代

    • Serial
      单线程收集器,使用复制算法,在垃圾收集期间,必须暂停其他所有工作线程,直到它收集结束。虚拟机运行在client模式下默认的新生代收集器。具有简单高效的优点。


      image.png
    • ParNew
      Serial收集器的多线程版本,支持线程并行去完成垃圾收集工作,使用复制算法,在垃圾收集期间,必须暂停其他所有工作线程,直到它收集结束。虚拟机运行在server模式下默认的新生代收集器。性能与Serial类似。
      ParNew收集器也是使用-XX:+UseConcMarkSweepGC选项后默认的新生代收集器,也可以使用-XX:+UseParNewGC选项来强制指定。
      ParNew默认开启的线程数与CPU数量相同,可以使用-XX:ParallelGCThreads来限制垃圾收集器的线程数。


      image.png
    • Parallel Scavenge
      并行多线程收集器,使用复制算法,区别于其他收集器的关注点:尽可能缩短垃圾收集时用户线程停顿的时间,Parallel Scavenge目标则是达到一个可控制的吞吐量。所谓吞吐量就是CPU用于运行用户代码时间与CPU总消耗时间的比值,吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)。参考ParNew配图。
      两个参数控制吞吐量:
      A、-XX:MaxGCPauseMillis 最大垃圾收集停顿时间
      B、-XX:GCTimeRatio 吞吐量大小,垃圾收集时间占总时间比率,相当于吞吐量的倒数。计算1/(1+n)。
      C、-XX:UseAdaptiveSizePolicy 自适应调节策略开关,把内存调优任务交给虚拟机去完成。

    2、老年代

    • Serial Old(MSC)
      单线程收集器。Serial收集器的老年代版本,使用标记-整理算法。主要给client模式下的虚拟机使用。


      image.png
    • Parallel Old
      多线程收集器。Parallel Scavenge收集器的老年代版本,使用标记-整理算法。注重吞吐量以及CPU资源敏感的场合,优先考虑Parallel Scavenge加Parallel Old收集器。


      image.png
    • CMS
      CMS目标是获取最短回收停顿时间。使用标记-清除算法。整个过程4个步骤:
      A、初始标记【stop the world】
      B、并发标记
      C、重新标记【stop the world】
      D、并发清除


      image.png

    3个缺点:
    A、CPU资源敏感:启动线程数=(CPU数量+3)/4,当CPU不足4个时,对用户程序影响较大。
    B、无法处理浮动垃圾:并发清理阶段产生的垃圾,使用参数-XX:CMSSInitiatingOccupancyFraction设置触发GC执行的老年代内存使用百分比。该参数设置过高易出行Concurrent Mode Failure异常(剩余内存不足以运行GC程序)。
    C、因使用标记-清除算法导致的空间碎片问题,开关参数-XX:+UseCMSCopactAtFullCollection是用在CMS收集器要进行FullGC时开启内存碎片的合并整理过程,参数-XX:CMSFullGCsBeforeComoaction用于设置执行多少次不压缩的FullGC后,跟着来一次带压缩的。

    3、横跨新老年代

    • G1
      G1目标是降低回收停顿时间,使用标记-整理算法。
      将java堆划分为多个大小相等的独立区域Region,G1跟踪各个Region垃圾堆积的价值大小(回收所获得的空间大小与回收所需时间的比值),维护一个优先列表,优先回收价值最大的Region,这种Region划分内存空间以及按优先级的区域回收方式,使得G1在有限的时间内获得尽可能高的收集效率。
      G1收集器分为4个过程:
      初始标记【stop the world】
      并发标记
      最终标记【stop the world】
      筛选回收


      image.png

    CMS与G1的区别:
    A、G1横跨新老年代,使用标记-整理算法,不会产生空间碎片;CMS是老年代收集器,使用标记-清除算法,会产生空间碎片。
    B、降低垃圾收集停顿时间是两者共同关注点,但是G1建立了可预测的停顿时间模型。
    C、G1将整个java堆划分为大小一致的region,跟踪各个region里面垃圾堆积的价值大小,维护一个优先列表,优先回收价值最大的region。

    垃圾收集算法
    • 标记-清除算法
      算法分为标记-清楚两个阶段:首先标记出所有需要回收的对象,然后统一回收所有被标记的对象。
      2个不足:
      A、 一个是效率低。两个过程效率都不高
      B、另一个是空间问题,标记清除之后会产生大量不连续的内存碎片,如果程序在运行过程中需要为较大对象分配内存时,可能找不到足够连续的内存而不得不提前触发另一次垃圾收集动作。


      image.png
    • 复制算法
      将内存分为大小相等两块,每次只使用其中一块。当这一块内存用完,就将还存活的对象复制到另外一块上面,再把已使用过的内存空间一次清理掉。
      解决1个问题:
      A、解决了标记-清除算法效率低的问题。


      image.png
    • 标记-整理算法
      算法分为标记-整理两个阶段:首先标记出所有需要回收的对象,让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
      解决2个问题:
      A、解决标记-清除算法空间不连续的问题。
      B、解决复制算法浪费50%内存的问题。


      image.png
    • 分代收集算法
      根据对象存活周期不同将java堆划分为新生代和老年代。新生代中,对象存活率低,因此选用复制算法。在老年代中,对象存活率高,选用标记-清除或标记-整理算法。

    二、内存分配

    • 新生代包括一个Eden区、两个Survivor区,内存比例为8:1。对象优先在Eden分配,如果设置了-XX:PretenureSizeThreshold参数,则令大于这个设置值的对象直接在老年代分配。如果Eden区内存不足以分配当前对象,则触发Minor GC,如果对象在Eden出生并经过一次Minor GC后仍然存活,如果能被Survivor容纳【否则通过担保机制移入老年代】,将被移动到Survivor空间,对象年龄加1,对象在Survivor每熬过一次Minor GC,年龄就加1岁,当年龄增加到老年代年龄阈值-XX:MaxTenuringThreshold,将被移入老年代。

    相关文章

      网友评论

          本文标题:2、自动内存管理:垃圾回收与内存分配

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