【深入理解JVM】6、GC算法-如何找垃圾+清除垃圾+JVM内存分代模型+常见的垃圾回收器【面试必备】

 上一篇:【深入理解JVM】5、运行时数据区(Run-time data areas)+常用指令【面试必备】

1、GC的基本知识

1、JAVA和C++ GC的区别

1、java

  • GC处理垃圾

  • 开发效率高,执行效率低

2、C++

  • 手工处理垃圾

  • 忘记回收垃圾

    • 内存泄漏

  • 回收多次

    • 非法访问

  • 开发效率低,执行效率高

2、如何定位垃圾。

1、RC:reference count 引用计数

  • 算法原理:给对象添加一个引用计数器,每当一个地方引用它时,计数器值就加1;每当一个引用时效时,计数器值就减1;当引用计数为0时,表示该对象不再使用,可以回收。
  • 应用:微软COM/ActionScript3/Python
  • 优势:实现简单,判定效率高,通常情况下是个不错的算法。
  • 不足:很难解决循环引用的问题。循环体为垃圾是但是他们的引用都不为0

2、RS:Root Searching 根可达算法

根可达算法
  • 算法原理:以称作“GC Roots”的对象作为起点向下搜索,搜索所走过的路径成为引用链,当一个对象到GC Roots不可达时,表示该对象不再使用,可以回收。
  • 应用:Java/C#/Lisp
  • 优势:可以解决循环引用的问题
  • 不足:算法略复杂
  • 根对象:GC roots(记住很重要)
    • 线程栈变量
    • 静态变量
    • 常量池
    • JNI指针:调用的C/C++引用的对象

3、清除垃圾的算法 GC Algorithms

  • Mark-Sweep(标记清除)

  • Copying(拷贝)

  • Mark-Compact(标记压缩)

1、Mark-Sweep(标记清除)

  • 先找到有用,再找到没有用的,然后标记,再清除

  • 特点:

    • 1、算法相对简单,存活对象比较多的情况下效率较高。

    • 2、需要两遍扫描(第一遍找到那些有用的对象,第二遍找到没有用的对象并清除),所以效率比较低,容易产生碎片(清掉的也不压缩也不清理,所以会有很多空的)

2、Copying(拷贝)

  • 把内存一分为2,把有用的拷贝到一半的内存,都拷贝完了之后,另一半的没有用的对象全部清除。

  • 找到一些有用的,然后将有用的复制拷贝到的别的地方去,然后拷贝完了再将这块没有用的对象全部删掉

  • 特点:

    • 1、适用于存活对象较少的情况,只扫描一次,效率提高,没有碎片

    • 2、空间浪费,移动复制对象,需要调整对象的引用。

3、Mark-Compact(标记压缩)

  • 先找到有用的对象然后移到前面去,让有用的对象全部到一个角落去,剩下的就是没有用的对象,全部清除

  • 特点:

    • 1、扫描两次,需要移动对象,效率偏低。

    • 2、不会产生碎片,方便对象分配,不会产生内存减半。

4.JVM内存分代模型(用于分代垃圾回收算法)

一个对象先栈上,装不下再往Eden区,垃圾回收一次进入survivor1区(幸存区域或者叫from)再回收一次进入survivor2区,经过多次垃圾回收之后进入Old区。

注释:GC:

  • MinorGC/YGC:年轻代空间耗尽时触发
  • MajorGC/FullGC:在老年代无法继续分配空间时触发,新生代,老年代同事进行回收
  • -Xmn: -X:非标参数。 m:Memory n:new
  • -Xms:s:最小值
  • -Xmx:x:最大值

  1. 部分垃圾回收器使用的模型

    除Epsilon ZGC Shenandoah之外的GC都是使用逻辑分代模型

    G1是逻辑分代,物理不分代

    除此之外不仅逻辑分代,而且物理分代

  2. 堆内空间(新生代 + 老年代 + 永久代(1.7)Perm Generation/ 元数据区(1.8) Metaspace

    1. 永久代 元数据 保存:- Class,方法编译完的信息,代码编译完的信息以及字节码等。
    2. 永久代必须指定大小限制(很容易溢出,这个还改不了) ,元数据可以设置,也可以不设置,无上限(受限于物理内存)
    3. 字符串常量 1.7 - 永久代,-->1.8 - 堆
    4. MethodArea(具体实现1.7 Perm Generation/ 元数据区(1.8) Metaspace)逻辑概念 - 永久代、元数据
  3. 新生代 = Eden + 2个suvivor区 

    1. YGC回收之后,大多数的对象会被回收,活着的进入s0
    2. 再次YGC,活着的对象eden + s0 -> s1
    3. 再次YGC,eden + s1 -> s0
    4. 年龄足够 -> 老年代 (15 CMS 6)
    5. s区装不下 -> 老年代
  4. 老年代

    1. 顽固分子
    2. 老年代满了FGC Full GC
  5. GC Tuning (Generation)

    1. 尽量减少FGC
    2. MinorGC = YGC
    3. MajorGC = FGC
  6. 动态年龄:(不重要) https://www.jianshu.com/p/989d3b06a49d

  7. 分配担保:(不重要) YGC期间(young gc) survivor区空间不够了 空间担保直接进入老年代 参考:https://cloud.tencent.com/developer/article/1082730

  8. 对象分配过程图:垃圾回收执行顺序:TLAB:线程本地分配。

对象分配过程图

 

了解即可:

 代码验证效率:

/**
 * 减号代表去掉属性
 * -XX:-DoEscapeAnalysis 去掉逃逸分析
 * -XX:-EliminateAllocations 去掉标量替换
 * -XX:-UseTLAB 去掉线程本地分配TLAB
 * -Xlog:c5_gc*
 * 逃逸分析 标量替换 线程专有对象分配
 */
public class TestTLAB {
    //User u;
    class User {
        int id;
        String name;

        public User(int id, String name) {
            this.id = id;
            this.name = name;
        }
    }

    void alloc(int i) {
        // 无逃逸
        new User(i, "name " + i);
    }

    public static void main(String[] args) {
        TestTLAB t = new TestTLAB();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000_0000; i++) t.alloc(i);
        long end = System.currentTimeMillis();
        System.out.println(end - start);

        //for(;;);
    }
}

正常运行:400

 修改参数运行:800

注释:

  • Eden区+S1-->S2超过50%就会丢到Old区如上图。

论证:(了解即可参考: https://www.jianshu.com/p/989d3b06a49d

uint ageTable::compute_tenuring_threshold(size_t survivor_capacity) {
    //survivor_capacity是survivor空间的大小
  size_t desired_survivor_size = (size_t)((((double) survivor_capacity)*TargetSurvivorRatio)/100);
  size_t total = 0;
  uint age = 1;
  while (age < table_size) {
    total += sizes[age];//sizes数组是每个年龄段对象大小
    if (total > desired_survivor_size) break;
    age++;
  }
  uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold;
    ...
}

我把晋升年龄计算的代码摘出。我们来看看动态年龄的计算。代码中有一个TargetSurvivorRatio的值。

-XX:TargetSurvivorRatio
目标存活率,默认为50%

  1. 通过这个比率来计算一个期望值,desired_survivor_size 。
  2. 然后用一个total计数器,累加每个年龄段对象大小的总和。
  3. 当total大于desired_survivor_size 停止。
  4. 然后用当前age和MaxTenuringThreshold 对比找出最小值作为结果

总体表征就是,年龄从小到大进行累加,当加入某个年龄段后,累加和超过survivor区域*TargetSurvivorRatio的时候,就从这个年龄段网上的年龄的对象进行晋升。

5、常见的垃圾回收器

注释:

  • 1-4:在逻辑上都是分代的,
  • 5-6:在逻辑上都不分代了。
  • 10:debug用的。
  • Serial指的是单线程系列,
  • Parallel指的是多线程。  
  • 常见组合:Serial+Serial Old;Parallel Scavenge+Parallel Old(如果现在很多线上没有做调优默认就是这个);ParNew+CMS组合见上面虚线。
  • CMS
  1. JDK诞生 Serial追随 提高效率,诞生了PS,为了配合CMS,诞生了PN,CMS是1.4版本后期引入,CMS是里程碑式的GC,它开启了并发回收的过程,但是CMS毛病较多,因此目前任何一个JDK版本默认是CMS 并发垃圾回收是因为无法忍受STW
  2. Serial 年轻代 串行回收 单CPU效率最高,虚拟机是Client模式的默认的垃圾回收器。 safe point(安全点)  stop-the-world(STW)(线程停止)在safe point上STW.现在用的极少
    Serial
  3. PS(Parallel Scavenge) 年轻代 并行回收  Parallel Scavenge的变种,是为了配合CMS使用
    Parallel Scavenge
  4. ParNew 年轻代 配合CMS的并行回收  PN响应时间优先,配合CMS,PS吞吐量优先。
    Parallel New
  5. SerialOld 
    Serial Old
  6. ParallelOld
  7. ConcurrentMarkSweep 老年代 并发的, 垃圾回收和应用程序同时运行,降低STW的时间(200ms) CMS问题比较多,所以现在没有一个版本默认是CMS,只能手工指定 CMS既然是MarkSweep,就一定会有碎片化的问题,碎片到达一定程度,CMS的老年代分配对象分配不下的时候,使用SerialOld 进行老年代回收 想象一下: PS + PO -> 加内存 换垃圾回收器 -> PN + CMS + SerialOld(几个小时 - 几天的STW) 几十个G的内存,单线程回收 -> G1 + FGC 几十个G -> 上T内存的服务器 ZGC 算法:三色标记 + Incremental Update
  8. G1(10ms) 算法:三色标记 + SATB
  9. ZGC (1ms) PK C++ 算法:ColoredPointers + LoadBarrier
  10. Shenandoah 算法:ColoredPointers + WriteBarrier
  11. Eplison
  12. PS 和 PN区别的延伸阅读: ▪https://docs.oracle.com/en/java/javase/13/gctuning/ergonomics.html#GUID-3D0BB91E-9BFF-4EBB-B523-14493A860E73
  13. 垃圾收集器跟内存大小的关系
    1. Serial 几十兆
    2. PS 上百兆 - 几个G
    3. CMS - 20G
    4. G1 - 上百G
    5. ZGC - 4T - 16T(JDK13)
  14. CMS
    1. 缺点:

1.8默认的垃圾回收:PS + ParallelOld

常见垃圾回收器组合参数设定:(1.8)

  • -XX:+UseSerialGC = Serial New (DefNew) + Serial Old

    • 小型程序。默认情况下不会是这种选项,HotSpot会根据计算及配置和JDK版本自动选择收集器
  • -XX:+UseParNewGC = ParNew + SerialOld

  • -XX:+UseConc(urrent)MarkSweepGC = ParNew + CMS + Serial Old

  • -XX:+UseParallelGC = Parallel Scavenge + Parallel Old (1.8默认) 【PS + SerialOld】

  • -XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old

  • -XX:+UseG1GC = G1

  • Linux中没找到默认GC的查看方法,而windows中会打印UseParallelGC 

    • java +XX:+PrintCommandLineFlags -version
    • 通过GC的日志来分辨
  • Linux下1.8版本默认的垃圾回收器到底是什么?

    • 1.8.0_181 默认(看不出来)Copy MarkCompact
    • 1.8.0_222 默认 PS + PO

下一篇:【深入理解JVM】8、JVM实战调优+arthas实战使用+jvisualvm实战使用+GC算法+JVM调优如何定位问题+线上排查+针对小BUG不停止服务修改bug+CMS+G1+常用参数【面试必备】​​​​​​​

猜你喜欢

转载自blog.csdn.net/zw764987243/article/details/109533242