垃圾收集器与内存分配策略

1,对象存活性检测

  1,引用计数器算法

    每增加引用+1,每引用失效-1,为0则未被使用

    优点:简单,高效

    缺点:不能解决对象循环依赖问题

    Hotspot未采用引用计数器算法

  2,可达性分析算法:GC Roots(Java中采用)

    GC Roots对象作为起始点,向下搜索引用链,不可及的对象则视为不可用

    GC Roots对象:

      1,虚拟机栈(栈帧本地变量表)中引用的对象

      2,方法区类静态属性引用的对象

      3,方法区常量引用的对象

      4,本地方法栈JNI引用的对象

  3,引用分类

    1,强引用:引用存在虚拟机永不回收

    2,软引用:内存溢出之前列入回收队列,回收后还是内存不足则oom,SoftReference

    3,弱引用:生存到下一次GC之前,WeakReference

    4,虚引用:毫无影响,只是在被回收的时候收到系统通知,PhantomReference

  强引用 > 软引用>弱引用>虚引用

  4,回收两次标记过程:

    1st:GC Roots引用链不可达&&对象finalize方法有必要执行,放入F-Queue队列,虚拟机开启一个Finalizer线程执行队列中对象的finalize方法

    2nd:对F-Queue队列中的对象第二次标记,(如果在对象的finalize()方法中将自己关联到GC Roots的引用链上,则不会回收),未逃离回收集合则被回收

  5,回收方法区(在永久代)

    主要是常量和类

    常量:没有引用则回收,包括类/接口/方法/字段的符号引用

    类:同时满足3个条件

      1,其所有实例都已经被回收

      2,加载该类的ClassLoader已经被回收

      3,该类的Class对象没有被引用,且无法通过反射访问该Class对象

2,GC 回收算法

  1,标记-清除算法(最基础的收集算法)

    分为标记(4,回收两次标记过程)和清除两个阶段

    不足:

      1,效率问题:标记和清除过程效率都不高

      2,空间问题:标记清除产生大量不连续的内存碎片,无法分配较大的对象(触发另一次GC)

  2,复制算法(解决效率问题)

      1,内存分为大小相等的两块,使用其中一块儿分配内存,回收时将仍然存活的对象复制到另外一块儿内存,一次清理当前内存块(没有内存碎片),分配内存时顺序分配

      优点:简单高效

      缺点:内存缩小一半

      2,jdk中,将内存分为一大(Eden空间)两小(Survivor空间),每次使用Eden+其中一块儿Survivor,回收时将Eden+和使用的那块儿Survivor中存活的对象复制到另外一个Survivor空间,清理掉原来的Eden+Survivor,默认Eden:Survivor=8:1

      3,老年代的分配担保

  3,标记-整理算法(老年代的回收)

    与标记-清除的区别:标记完成后不清理,而是将存活的对象移动到内存的另一端,清理掉边界外的内存(对象)

  4,分代收集算法

    根据对象生命周期将内存划分为几块,一般划分为新生代和老年代,新生代回收用复制算法,老年代回收用标记-清理,标记-整理算法

 

堆区:

新生代

  Eden,Survivor1,Survivor2

老年代

 

3,

  1,枚举根节点 

    一致性,STW,OopMap

  2,safepoint

    让程序长时间执行的特征作为条件

  3,safeRegion

    代码片段中,引用关系不会发生变化

3,垃圾回收器

  

  新生代:Serial,ParNew,Parallel Scavenage, G1

  老年代:CMS,Serial Old(MSC),Parallel Old,G1

  新老搭配关系:

  Serial---->CMS,Serial Old

  ParNew---->CMS,Serial Old(MSC)

  ParallelScanvenge----->Serial Old,Parallel Old

  //------------------------------------------------

  G1---->G1

  CMS---->Serial,ParNew,Serial Old(老)

  Serial Old--->CMS(老),Serial,ParNew,Parallel Scanvenge

  Parallel Old ---->Parallel Scanvenge

  G1-----> G1  

 

  1,Serial收集器

    最基本的,STW,单线程

    Client下新生代默认的收集器,因为新生代比较小,简单高效

     复制算法

    

 

  2,ParNew收集器

    Serial的多线程版本

    采用复制算法

    Server模式下首选的新生代收集器,新生代只有ParNew和Serial能配合CMS(老年代)工作

    单核CPU效果不明显

  

  3,Parallel Scavenge收集器

    复制算法,多线程

    吞吐量优先,吞吐量=运行用户代码时间/(运行用户代码时间+GC垃圾收集时间),高吞吐量适合后台运算交互不多的任务

    GC自适应调节策略 -XX:UseGCAdaptiveSizePolicy

  4,Serial Old收集器

    标记-整理算法,单线程

    Client模式下使用,CMS收集器的后备预案

    图见Serial收集器

  5,Parallel Old收集器

    标记-整理算法,多线程

    注重吞吐量和CPU资源敏感的场景,Parallel Scanvenge + Parallel Old

  

  6,CMS收集器

    目标:最短回收停顿时间,注重相应性能

    标记-清除算法

    4个步骤:

      初始标记:STW,标记GC Roots直接关联的对象,很快

      并发标记:GC Roots Tracing,耗时长,可以用用户线程并发

      重新标记:修正并发期间用户线程产生的标记变动,时间短于并发标记,长于初始标记

      并发清除:可以用用户线程并发

    优点:并发收集,低停顿

    缺点:

      对CPU资源比较敏感,CPU数量少的时候,占用较多CPU资源,改进算法i-CMS(然并卵)

      CMS无法处理浮动垃圾,并发清理时用户线程也在生成垃圾,在Concurrent Mode Failed时,后备预案,临时启用Serial Old来收集老年代,时间变长

      大量空间碎片,标记-清除算法的通病,内存碎片的合并整理

  

 

  7,G1收集器

    目标是替换调CMS

    整体来看是标记-整理算法

    特点:

      并行与并发:多CPU缩短STW时间,用户程序仍在并发

      分代收集:单独的算法处理新创建对象和老对象

      空间整合:Region局部采用复制算法,不会有空间碎片

      可预测停顿:可预测停顿时间模型,降低停顿时间

    内存布局的变化:没有新生代和老年代的区别,所有都划分为等大小的Region区,新老不再物理隔离

    避免全区域垃圾回收,根据Region判断回收价值,回收价值最大的Region(G1),有限时间获取更高的回收效率

    可达性判断问题更突出,采用Region的RemeberSet来处理

    回收过程:

      初始标记

      并发标记

      最终标记:并发标记将对象变化记录在线程的Remebered Set Log,在这个阶段将RemeberSetLog中的变化记录到RememberSet中

      筛选回收

    

 4,内存分配和回收策略

  在堆(Heap)上分配(JIT标量类型间接在栈上分配),主要是在Eden区上分配,TLAB,少数直接在老年代分配

  对象优先在Eden区分配(分配)

    Eden区空间不足,触发Minor GC

    新生代(Eden区+一个Survivor区)

    新生代GC:Minor GC

    老年代GC:Full GC/Major GC,Full GC经常伴随至少一次Minor GC   

  大对象直接进入老年代(分配)

    -XX:PretenureSizeThreshold,大于这个参数值的对象直接分配在老年代(只对ParNew和Serial收集器有效)

  长期存活的对象将进入老年代

    对象年龄计数器,每熬过一次Minor GC,则年龄计数器+1,达到阈值(-XX:MaxTenuringThreshold,默认15)则对象晋升到老年代

  动态对象年龄判断

    Survivor中相同年龄X的对象大小总和 > 1/2 Survivor空间,则对象年龄>=X的对象直接进入老年代

  空间分配担保

    

 

posted on 2017-12-11 21:17  Vindia  阅读(268)  评论(0)    收藏  举报

导航