代码改变世界

深入理解Java虚拟机2-chap3-斗之气9段

2019-03-02 17:21  剑动情缥缈  阅读(246)  评论(0编辑  收藏  举报

一、GC需要完成三件事

  1. 哪些内存需要回收:找出不需要使用的对象
  2. 什么时候回收:JVM空闲/堆内存紧张
  3. 如何回收:回收垃圾的策略

二、寻找已死对象:第一件事

  判断对象是否存活算法

  1.引用计数算法

  • 原理:给对象添加一个引用计数器,每当有一个地方引用本对象,计数器值加1,无法解决对象之间循环引用问题
  • 代码:对象A中引用指向B,B反之,当引用置为null后,堆中的对象仍然在互相引用着
package com.chengjie.C3;

class ReferenceCountGC {
    public Object instance = null;
    private static final int _1MB = 1024 * 1024;

    private byte[] bigSize = new byte[2 * _1MB];

    public static void testGC() {
        ReferenceCountGC objA = new ReferenceCountGC();
        ReferenceCountGC objB = new ReferenceCountGC();
        objA.instance = objB;
        objB.instance = objA;
        objA = null;
        objB = null;
        System.gc();
    }
}

public class T3_1 {
    public static void main(String[] args) {
        ReferenceCountGC.testGC();
    }
}
View Code

  2.可达性分析算法(主流方法)

  • 原理:通过一系列称为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明对象不可用
  • 可作为GC Roots的对象包括:

  ①虚拟机栈中引用的对象;

  ②方法区中类静态变量引用的对象;

  ③方法区中常量引用的对象;

  ④本地方法栈JNI(即一般的Native方法)引用的对象。

  

  引用分类

  1.强引用

  2.软引用

  3.弱引用

  4.虚引用

  对象真正生死时刻

  即使在可达性分析中不可达的对象,也并非非死不可,只是缓刑,真正宣告一个对象死亡,需要经历两次标记过程

  1.对象在可达性分析中不可达,判断其是否有必要执行finalize()方法?

  ①有必要:选出来进入F-Queue

  ②无必要:等待回收

  2.在低优先级线程上执行finalize()方法,看其是否重新建立与GC Roots中的对象建立关联?

  ①建立:逃脱回收

  ②未建立:等待回收

  

  注:对象的finalize()方法只会被系统调用一次,即只能自我拯救一次

三、垃圾回收算法

  1.标记-清除算法

  • 原理:标记处所有需要回收的对象,标记完后统一回收所有被标记的对象

  

  • 优点:
  • 缺点:

  ①效率:标记、清扫效率低

  ②空间:会产生大量内存碎片,当分配一个对象由于连续内存不足,会触发对于的GC

  2.复制算法

  • 原理:将内存空间分为两块,一块内存用完则将存活对象复制到另一块,然后把前一块内存清理掉

  

  • 优点:

  ①无内存碎片问题

  ②实现简单,分配效率高,只需要移动指针即可

  • 缺点:

  ①浪费了一半内存

  3.标记-整理算法

  • 原理:标记完对象后,非直接将可回收对象清理,而是将存活对象向一段移动,然后清理掉边界外的内存

  

  • 优点:

  ①无内存碎片问题

  • 缺点:

  ①标记过程效率低

  4.分代收集算法

  • 原理:根据对象存活周期的不同将内存分为几块,一般为新生代与老年代

  ①新生代:新生代每次垃圾回收有大量对象死去,故采用复制算法

  ②老年代:对象存活率高,使用标记-清除或者标记-整理算法

  • 优点:分代回收,资源利用率高,效率高

四、HotSpot算法实现细节

  1.枚举根节点

  • GC进行可达性分析时,必须STW,即使是CMS收集器
  • 当系统STW,JVM不需要挨个检查所有上下文和引用,通过OopMap数据结构可以直接知道哪些地方存放对象引用
  • OopMap:类加载完成时,HotSpot把对象内什么偏移量上是什么类型的数据计算出来

  2.安全点

  • 并非为任何指令都生成OopMap,而是在能确保具有让程序长时间执行的特征的那些点
  • GC发生时,如何让所有线程运行到安全点停顿下来

  ①抢先式中断

  ②主动式中断

  3.安全区域

  • 有些内存未分配cpu,无法响应JVM的中断请求,JVM不可能等待其执行并进入安全点
  • 解决方案:设置安全区域,此区域引用关系不发生变化

五、垃圾收集器

   

新生代垃圾收集器

  1.Serial:单线程

  

  2.ParNew:多线程

  

  3.Parallel Scavenge:ParNew的升级版,更关注吞吐率

  

老生代垃圾收集器

  1.CMS:获取最短回收停顿时间

  • 优点:

  ①耗时很长的并发标记与并发清楚过程都未STW

  • 缺点:

  ①吞吐量低

  ②无法处理浮动垃圾

  ③基于标记-清除,会产生内存碎片,可能提前触发Full GC

  

  2.Serial Old(MSC)

  

  3.Parallel Old

  同新生代的Parallel Scavenge

G1垃圾收集器

  • 优点:

  ①并行

  ②多线程

  ③垃圾回收率高

  ④分代收集:新生代(复制) 老生代(标记-整理)

  ⑤不会产生内存碎片

  • 工作流程

  ①初始标记

  ②并发标记

  ③最终标记

  ④筛选回收:可预测停顿,根据设置阈值,选择回收哪些

  

六、内存分配与回收策略

  对象主要分配在新生代的Eden区,若启动了本地线程分配缓冲,将按线程优先在TLAB上分配,少数情况下可能直接分配在老年代

  分配原则

  1.对象优先在Eden分配

  2.大对象直接进入老年代:避免在Eden与Survivor区之间发生频繁拷贝

  3.长期存活对象将进入老年代:熬过一次Minor GC,年龄长1

  4.对象年龄判断:Survivor区相同年龄对象总和达到区空间的一半,年龄大于等于的对象可以进入老年代

  5.空间分配担保:

  ①Minor GC发生前,JVM会检查老年代可用最大连续空间是否大于新生代对象总和,如成立,则安全;

  ②若不成立,看是否设置了允许担保失败值,若允许,检查老年代可用最大连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,则尝试一次Minor GC,这是有风险的;

  如果小于,或者未设置允许担保失败,则进行一次Full GC

参考:

https://www.jianshu.com/p/0269237a229d

https://gavinzhang1.gitbooks.io/java-jvm-us/content/la_ji_shou_ji_qi_yu_nei_cun_fen_pei_ce_lve.html