zoukankan      html  css  js  c++  java
  • 第三章 垃圾收集器与内存分配策略

      

    3.2 哪些对象需要回收

      3.2.1 引用计数法

      对象持有一个计数器,有地方引用则计数器加1,当引用失效时减1,计数器为零时回收。存在环形引用无法回收问题。

      3.2.2 可达性分析

      当对象和GC Roots对象之间没有引用路径时,需要回收。

      GC Roots对象

    1. 虚拟机栈中引用的对象
    2. 方法区中静态属性引用的对象
    3. 方法区中常量引用的变量
    4. 本地方法栈中引用的对象
    5. 基本数据类型对应的Class对象,常驻异常对象,系统类加载器
    6. 被同步锁持有的对象

      除此之外,当局部回收时,如果有对象被回收区域以外的对象引用,也需要把引用的对象加入GC Root对象中。

      3.2.3 引用关系类型

    1. 强引用关系:Object o = new Object();只要还有引用关系,被引用对象不会被回收。
    2. 软引用关系:SoftReference<Object> reference  = new SoftReference<>(new Object());在将要发生OOM时回收;
    3. 弱引用关系:WeakReference<Object> reference = new WeakReference<>(new Object());当垃圾收集发生时,就会回收;
    4. 虚引用关系:对对象的生存时间不造成影响,无法通过虚引用找到对象实例。

      3.2.4 是否需要回收

    1. 与GC Roots对象有没有引用路径
    2. 是否需要执行finalize()方法。若没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,则判定为没有必要执行,直接回收。如果需要执行,放入F-Queue队列中,有一个Finalizer线程启动finalize()方法,如果在该方法中重新关联到GC Roots的引用链上,可以避免回收。

      3.2.5 回收方法区

    1. 废弃的常量
    2. 不再使用的类型:
      1. 该类及派生子类的所有实例都被回收
      2. 加载该类的类加载器被回收
      3. Class对象没有在任何地方被引用,无法通过反射访问该类的方法

    3.3 垃圾收集算法

    • Partial GC(部分收集)
      • Minor GC(新生代收集)
      • Major GC(老年代收集)
      • Mixed GC(混合收集)
    • Full GC(整堆收集)

      3.3.1 分代收集理论

      当前商业虚拟机的垃圾收集器,大都遵循了"分代收集"理论进行设计,"分代收集"理论是符合

    1. 弱分代假说:绝大多数对象都是朝生夕灭
    2. 强分代假说:熬过越多次垃圾收集过程的对象越难被回收

       收集器应该将java堆划分成不同的区域,不同区域的回收频率不同。但是,不同区域之间的对象可能存在引用关系

      3. 跨代引用假说:跨代引用相对于同代引用仅占极少数

       根据假说3,不需要在跨代引用时扫描整个老年代,只需要在新生代上建立一个全局的数据结构(记忆集),将老年代划分成若干小块,标志出哪一个小块存在跨代引用,在Minor GC时,只有包含了跨代引用的小块内存才加入到GC Roots进行扫描。在对象引用关系改变时,会增加一些维护开销。

      3.3.2 标记-清除算法

      标记需要清除的对象,统一回收。

      缺点:(1)标记和清除操作开销随垃圾对象增加而增加 (2)内存碎片化

      3.3.3 标记-复制算法

      将内存空间划分成两部分,每次使用其中一部分,需要清理时,将保留的对象复制到另一部分内存中,然后整块清除。

      缺点:(1)内存使用率低 (2)当存活的对象很多时,复制操作消耗高

      3.3.4 标记-整理算法

      在标记后,将存活的对象移动到内存空间的一端,清理掉边界以外的内存。

      缺点:(1)当存活的对象很多时,移动操作消耗高,且需要暂停用户程序(ZGC和Shenandoah除外)

    3.4 HotSpot算法实现细节

      3.4.6 可达性分析

      在并发标记阶段,标记线程和用户线程同时运行,一个对象的的引用关系,在标记之后可能会发生变化。

      三色标记  

      黑色节点:当前对象被垃圾回收器访问,且所有引用都以扫描,安全存活,本次回收中不会被再次扫描

      灰色节点:当前对象被垃圾回收器访问,至少有一个引用未被扫描

      白色节点:当前对象未被垃圾回收器访问,标记开始阶段,所有节点都是白色,在标记结束后,若还是白色,则会被回收

      并发标记的问题:

    1. 标记为黑色的变为白色:浮动垃圾
    2. 标记为白色的变为黑色:对象消失

      当且仅当以下两个条件同时满足时,会导致对象消失

    1. 新增了一条从黑节点到白节点的引用
    2. 删除了一条从灰节点到白节点的直接/间接引用

      所以解决对象消失问题的办法即破坏其中一个条件:

    1. (破坏条件1)增量更新:每增加一个黑节点到白节点的引用,记录新增引用,标记完毕后,以每个引用上的黑色节点为根,重新扫描。应用在CMS回收器
    2. (破坏条件2)原始快照:每删除一条灰节点到白节点的引用,记录删除的引用,标记完毕后,以每个引用上的灰色节点为根,重新扫描。应用在G1回收器

      应用原始快照的G1回收器,为了解决新增白色节点引用黑色节点导致对象消失的问题,G1为每个Region设计了两个名叫TAMS的指针,在Region中划分了一部分内存用于并发标记阶段产生的新对象,这些新对象的内存地址保证在TAMS指针指向的地址之上。G1默认这部分内存地址都是隐形标记的,默认存活。

      CMS和G1只解决了对象消失的问题,依然会产生浮动垃圾。

    3.5 经典垃圾收集器

      并行:多个垃圾收集线程配合工作

      并发:垃圾收集线程和用户线程同时进行

      3.5.1 Serial收集器/Serial Old收集器

      单线程。新生代采用复制算法,老年代采用标记-整理算法。垃圾收集期间暂停用户线程。

      3.5.2 ParNew 收集器

      并行。新生代采用复制算法。垃圾收集期间暂停用户线程。因为多个回收线程并行,吞吐率高,但停顿时间会比较长。

      3.5.3 Parallel Scavenge收集器

      吞吐率:运行用户代码的时间/(运行用户代码的时间+运行垃圾收集的时间)

      并行。可控的吞吐率,高吞吐率可以最高效率地利用处理器资源,尽快完成程序的运算任务,适合在后台运算而不需要太多交互的任务。低吞吐率,则减少系统的卡顿感,但延迟了垃圾线程的工作时间,适合和用户交互的任务。

      3.5.4 Parallel Old收集器

      Parallel Scavenge收集器的老年代版本。

      3.5.5 CMS收集器

      老年代垃圾处理器,标记-清除

      • 初始标记(标记GC Roots) 单线程,暂停用户线程。
      • 并发标记(标记引用链)并发
      • 重新标记(修正并发标记阶段引用关系变化的部分)并行,暂停用户线程
      • 并发清除(清除垃圾对象)并发

      优点:以最短停顿时间为目标,停顿发生在初始标记和重新标记阶段,初始标记时间很短,而因为并发标记的关系,重新标记也很短,适合跟用户交互的系统。

      缺点:并发阶段,占用了处理器资源,降低了吞吐量。在并发阶段,由于用户线程依然运行,会产生浮动垃圾,所以不能等到老年代空间快满时才回收。因为是标记整理会产生内存碎片。

      改进

    1. 针对浮动垃圾,CMS默认当内存占用达到一定比例时即开始回收。JDK5默认68%,JDK6默认92%。如果在并发标记阶段内存不够分配,将会启用Serial Old收集器进行老年代垃圾回收,会产生长时间停顿。
    2. 针对内存碎片,提出了两个方案:(1)在即将进行Full GC时进行内存整理,无法并发 (2)在若干次不进行整理的Full GC之后,下一次进入Full GC之前进行整理 两种方案均在JDK9废弃
    3. CPU敏感问题,并发阶段,垃圾收集线程数(cpu核心+3)/4,当cpu不足4时,占用线程数高导致应用程序变慢,因此提出了增量式并发收集器,在并发阶段,交替执行回收线程和用户线程。延长了回收时间,但速度变慢减缓。在JDK9被废弃

      3.5.6 G1收集器

      将内存区域划分成等量大小的Region块,保留新生代和老年代的概念。将Region作为单次回收的最小单元,按照每个Region回收的价值大小维护一个优先级列表,根据用户设定的停顿时间决定回收哪些区域。在延迟可控的情况下获得尽可能高的吞吐量。

      • 初始标记(标记GC Roots直接关联的对象)单线程,暂停用户线程。
      • 并发标记(标记引用链)并发
      • 最终标记(标记引用链)并行,暂停用户线程。
      • 筛选回收(根据回收价值对Region排序,根据用户期望的停顿时间制定回收计划)暂停用户线程。

      缺点:占用内存比大约相当于java堆容量的10%~20%。

      3.5.7 高吞吐量和低停顿

      吞吐量高指的是用户线程占用CPU时间多,低停顿是指GC时stop the world事件比较短。这两者有时是相冲的,思考一下,一个GC任务,如果全力去做只要1秒钟,但是会暂停用户线程,造成卡顿,那么我们将其拆散成多个小任务,每次花费200毫秒,虽然停顿事件短了,但因为上下文切换等问题,拆成的任务多达8个,那么总的回收时间则是1.6秒,降低了吞吐量。

    3.8  内存分配和回收策略

    • 对象优先在Eden分配,当空间不足时,触发Minor GC。
    • 大对象直接进入老年代,譬如很长的字符串和元素很多的数组。避免高额的内存复制开销。直接进入老年代也是为了避免在新生代的Eden空间和两个Survivor空间来回复制产生的大量复制操作。
    • 长期存活的对象将进入老年代,虚拟机给每个对象定义了一个年龄计数器,存放在对象头中,当对象从Eden晋升到Survivor中,年龄增加1,此后每次在Minor GC中存活,则年龄增加1。当达到设定值时,荣升老年代。
    • 动态对象年龄判定,当然也不是必须要达到设置值时才会进入老年代,考虑到极限情况,当一个年龄层的对象总数大于Survivor一半的时候,该年龄被判定为高龄,这之上(包含)的对象都将进入老年代。
    • 空间分配担保,在发生Minor GC之前,老年代会检查剩余空间是否能容纳新生代所有对象,如果不行,则查看是否允许担保失败(-XX:HandlePromotionFailure),如果允许,则根据历史经验判断,老年代是否能容纳以往进入老年代对象的平均大小,如果可以,则进行一次有风险的Minor GC,如果不行或者不允许担保失败,那么则进行Full GC。

    3.9 两个Survivor区

      新生代,一次只用Eden和一个survivor。比如使用Eden和survivor0,发生回收后,将剩余的对象复制到survivor1,并清除Eden和survivor0,下次使用Eden和survivor1。这样的好处是,避免产生内存碎片

     

    人生就像蒲公英,看似自由,其实身不由己。
  • 相关阅读:
    [学习记录]Flask会话维护
    [学习记录]MarkDown语法
    [学习记录]jinja2模板语法
    [学习记录]flask资源加载
    [学习记录]flask初步
    [常用操作]使用github桌面版上传代码
    [学习记录]简明扼要的Sass
    接口Mock测试
    12 | 从0到1:你的第一个GUI自动化测试
    navicat导出DDL语句
  • 原文地址:https://www.cnblogs.com/walker993/p/12893510.html
Copyright © 2011-2022 走看看