《深入理解Java虚拟机》学习笔记(三)垃圾回收

  1. 判断对象是否存活
    1. 引用计数算法:为对象添加引用计数器,引用计数器+1,引用失效计数器-1,算法简单,效率高,很难解决对象间相互循环引用的问题。
    2. 可达性分析算法:通过一系列“GC Roots” 的对象作为起点,从这些节点向下开始搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链,证明此对象不可用。
  2. 引用
    1. 强引用:例如 Object obj = new Object();只要引用还存在,立即收集器永远不会回收掉被引用的对象;
    2. 软引用:还有用但不是必要的对象,在系统将要发生内存溢出之前,将这些对象列入回收范围,进行第二次回收;
    3. 弱应用:非必须对象,强度稍弱于软引用,被弱引用的对象只能生存到下次垃圾回收前;
    4. 虚引用:‘幽灵引用’或‘幻影引用’,最弱的引用,对对象的生命周期没有任何影响,唯一目的是在这些对象被回收前发出通知;
  3. 方法区回收:判定一个常量是否是“废弃常量”比较简单,判定一个类是否是“无用类”的条件比较苛刻
    1. 该类所有的实例都已被回收,java堆中,不存在任何该类的任何实例;
    2. 加载该类的ClassLoader已经被回收;
    3. 该类对应的java.lang.Class对象没有任何地方被引用,没有办法在任何地方通过反射访问;
    在大量使用反射、动态代理、CGLib等Bytecode框架、动态生成JSP以及OSGi这类频繁自定义ClassLoader的场景都需要虚拟机具备【类卸载】功能,以保证永久代不会溢出;
  4. 垃圾收集算法
    1. 标记-清除算法:首先标记出需要回收的对象,在统一将完成回收,不足:1.效率问题,标记和清除效率都不高;2. 空间问题,标记清除后产生大量不连续的内存碎片,在程序需要大内存时,无法找到足够的内存空间而不得不提前触发一次GC
    2. 复制算法:将内存划分为大小相等的俩块,当一块内存用完,触发垃圾回收,将存活的对象复制到另一块上,将已使用过的那块全部清除掉,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行效率高,只是代价是将原有内存缩小了一半。现在商业虚拟你还在采用这种算法来回收新生代,IBM专门研究表明,新生代中98%的对象是‘朝生夕亡’,所以不需要按照1:1的比例分配内存空间,而是内存分为较大的一块Eden空间和俩块较小的Survivor空间,每次使用Eden和其中一块Survivor,每次回收,将Eden和Survivor中存活对象复制到另一块Survivor中,清理到Eden和Survivor,HotSpot虚拟机默认默认的比例是8:1,只有10%的内存会被浪费掉,98%会被回收是一般情况下,我们没有办法保证,所以当Survivor空间不够用时,需要依赖其它内存空间(老年代)进行分配担保,如果一块Survivor无法放下上次存活的对象,将根据分配担保机制直接进入老年代中。不足,在对象存活率较高时,效率将会很低,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对内存100%存活的极端情况,所以老年代一般不能直接选用复制算法。
    3. 标记-整理算法:标记仍与标记清除一样,但后续不是将可回收对象进行清理,而是让所有存活的对象都向一段移动,然后清理掉比边界意外的内存;
    4. 分代收集算法:根据对象存活周期的周期不同将内存划分为几块,一般是将Java堆分为新生代、老年代。这样可以根据各个年代的特点采用不同的垃圾收集算法,
  5. 垃圾收集器

    《深入理解Java虚拟机》学习笔记(三)垃圾回收
    文章图片
    HotSpot虚拟机垃圾收集器.jpg(图片来源百度)
    【《深入理解Java虚拟机》学习笔记(三)垃圾回收】七种不同的分代收集器,如果来个收集器间有连线,代表可以搭配使用,没有最好的收集器,自由最合适的,
    1. Serial收集器(串行收集器)
      最基本,发展历史最悠久的收集器,JDK1.3前的虚拟机新生代的唯一选择,单线程收集器,收集时必须暂停所有其它工作线程,直到收集完成,“Stop The World”;
    2. ParNew收集器
      Serial多线程版,与CMS结合使用;
    3. Parallel Scavenge
      新生代收集器,使用复制算法,并行的多线程收集器,看上去和ParNew一样,控制吞吐量,也称“吞吐量优先”收集器;
    4. Serial Old收集器
      Serial Old是Serial的老年代版本,单线程“标记-整理”算法;
    5. Parallel Old收集器
      Parallel Old 是Parallel Scavenge 收集器的老年代收集器,多线程“标记-整理”算法,JDK1.6开始提供;
    6. CMS收集器
      获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用集中在互联网或B/S系统的服务器上,这类应用尤其重视服务器的响应速度,希望系统停顿时间最短,以给用户带来较好的体验,CMS非常符合这类应用的需求。基于“标记-清除”算法实现,过程分为4个步骤
      1. 初始标记 : 仅仅只是标记GC Roots能直接关联到的对象,速度快,
      2. 并发标记:GC Roots Traction的过程,
      3. 重新标记:修复并发标记期间因用户程序继续运行而产生的变动
      4. 并发清除:清理
        其中初始标记、重新标记仍然需要“Stop The World”, 耗时较长的并发标记和并发清理都是和用户线程一起运行的,总体来说CMS并发收集、低停顿;
        不足:
        1. 对CPU资源非常敏感:在并发阶段,虽然不会导致用户程序停顿,但是会占用一部分线程,导致程序变慢,影响总吞吐量,CMS默认启动回收线程数量(cpu数量+3)/4,4个以上占用25%;
        2. 无法处理浮动垃圾:浮动垃圾就是在进行并发清理时,用户程序并发执行产生的垃圾,这部分垃圾只能等下次GC。所以不能等到永久代完全填满再进行GC,所以要预留一部分数据;
        3. CMS是基于标记-清理算法的,产生大量碎片,无法为大对象分配空间,不得不提前出发一次Full GC;
    7. G1收集器
      是一款面向服务的垃圾收集器,具备以下优势
      1. 并行与并发:G1可以充分利用多CPU、多核环境下的硬件优势,使用多核缩短Stop The World停顿时间,
      2. 分代收集:虽然G1可以不需要其它收集器配合就能独立完成整个GC,但是他可以采用不同的方式处理新建对象与已存活一段时间、多个GC的就对象获取更好的收集效果;
      3. 空间整合:使用标记-整理算法;
      4. 可预测停顿:低停顿是G1和CMS共同关注的点,但G1除了追求低停顿外,还能建立预测的停顿时间模型,能为使用者明确指定在一个长度为M毫秒的时间片断内,消耗在垃圾收集上的时间不得超过N毫秒。
      G1将整个堆化为大小相等的独立区域(Region),仍然保留新生代和老年代的概念,但不再是物理隔离,G1根据各个Region里面的垃圾堆积的价值,后台维护优先队列,每次根据允许收集时间,优先回收价值最大的Region,Region之间的对象引用以及其他收集器中的新生代和老年代之间的对象引用,虚拟机都是使用Remembered Set来避免全堆扫描的,每个Region中都对应一个Remembered Set,
      G1回收流程
      1. 初始标记:标记GC Roots直接关联到的对象
      2. 并发标记:可达性分析,找出存活对象,
      3. 最终标记:修正并发标记期间的标记,
      4. 筛选回收:将各个Region的回收价值和成本进行排序,根据用户期望的GC停顿时间来指定回收计划。

    推荐阅读