C/C++教程

GC基础知识

本文主要是介绍GC基础知识,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

1.什么是垃圾

没有任何引用指向的一个对象或者多个对象(循环引用)

2.如何定位垃圾

  1. 引用计数(ReferenceCount)

    就是对每个对象都追踪指向它们的引用数,如果引用数为0,就说明这个对象是内存垃圾了。但是这个方法存在缺陷,如果多个对象之间存在循环引用,但是这些对象没有被外部引用,这些对象实质上也是垃圾,但是没有办法被标记

  2. 根可达算法(RootSearching),jvm采用的是这种

    指从根对象开始扫描,所有可以被扫到的对象都被标记为存活对象,而扫不到的对象就是垃圾对象。对扫到的对象一般是在该对象的header中打上一个标识,表示当前对象还是存活对象。

    那么什么是根对象呢?主要由下面四种对象构成:

    • main方法的栈帧中的对象
    • class文件中的静态变量
    • 常量池中的对象
    • JNI用到的对象

3.常见的垃圾回收算法

  1. 标记清除(mark sweep)

    image

    • 执行流程:标记阶段,从根对象开始进行遍历,对根对象可以访问到的对象都打上一个标识,一般在对象头中,将其记录为可达对象;在清除阶段对堆内存从头到尾进行遍历,如果发现某个对象没有标记为可达对象(通过读取对象头信息)则就将其回收
    • 优点:算法简单,存活对象比较多的时候效率较高,适合old区
    • 缺点:位置不连续容易产生碎片,两遍扫描效率偏低
  2. 拷贝算法 (copying)

    image

    • 执行流程:从根对象开始进行遍历,对根对象可以访问到对象移动到一个专门的内存区域(java中叫s0、s1),然后回收这块内存区域之外的其他内存区域
    • 优点:没有碎片,只扫描一次效率提高,适合存活对象较少的情况,比如年轻代
    • 缺点:浪费空间,移动复制对象需要调整对象引用
  3. 标记-整理(mark compact)

    image

    • 执行流程:标记阶段,从根对象开始进行遍历,对根对象可以访问到的对象都打上一个标识,一般在对象头中,将其记录为可达对象;整理阶段,遍历整个堆,将存活对象全部向一端移动,然后直接清理掉边界以外的内存

    • 优点:不会产生碎片方便对象分配,不会存在内存减半问题,适合old区

    • 缺点:扫描2次,需要移动对象,效率偏低

4.JVM内存分代模型

  1. 内存分代模型

image

  1. GC概念

image

  • MinorGC/YGC:年轻代空间耗尽时触发

  • MajorGC/FGC:在老年代无法继续分配空间时触发,新生代老年代同时进行回收

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

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

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

    除此之外其他垃圾回收器不仅逻辑分代,而且物理分代

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

    永久代 元数据 - Class

    永久代必须指定大小限制 ,元数据可以设置,也可以不设置,无上限(受限于物理内存)

    字符串常量 1.7 - 永久代,1.8 - 堆

    MethodArea逻辑概念 - 永久代、元数据具体实现

    https://blog.csdn.net/qq_27062249/article/details/116952519

  3. 新生代 = Eden + 2个suvivor区

    YGC回收之后,大多数的对象会被回收,活着的进入s0

    再次YGC,活着的对象eden + s0 -> s1

    再次YGC,eden + s1 -> s0

    年龄足够 -> 老年代 (年龄限制XX:MaxTenuringThreshold :其他垃圾回收器15,CMS 6)

    s区装不下 -> 老年代

  4. 老年代

    顽固分子

    老年代满了FGC Full GC

  5. GC Tuning (Generation)

    尽量减少FGC

    MinorGC = YGC

    MajorGC = FGC

  6. 栈上分配

    1. 什么是栈上分配:对象在栈上分配而不是堆
    2. 栈生分配优点:不需要GC介入去回收这个对象,出栈即释放资源,可以提高性能
    3. 哪些对象可以在栈上分配:线程私有小对象、无逃逸、支持标量替换
    4. 如何开启栈上分配:jvm默认支持,可以通过 -XX:-DoEscapeAnalysis关闭逃逸分析, -XX:-EliminateAllocations关闭标量替换,从而关闭栈上分配,一般情况下无需调整
  7. 线程本地分配TLAB(Thread Local Allocation Buffer)

    1. 什么是本地分配:JVM为每一个线程在初始化时先单独分配一块Buffer,该空间是线程私有的,当线程执行时需要为对象分配内存时,就是用自己的Buffer空间分配,这样就解决了多线程因共用同一JVM堆空间而产生的同步操作冲突问题。TLAB使用的堆内存空间其实还是eden区,默认1%
    2. 本地分配优点:多线程的时候不用竞争eden就可以申请内存,提高效率
    3. 如何开启本地分配:默认开启,可以通过-XX:-UseTLAB来关闭TLAB,一般情况下无需调整
  8. 动态年龄

    1. 年龄从小到大进行累加,当加入某个年龄段后,累加和超过survivor区域*TargetSurvivorRatio的时候,就从这个年龄段往上的年龄的对象进行晋升。比如年龄1的占用了33%,年龄2的占用了33%,累加和超过默认的TargetSurvivorRatio(50%),年龄2和年龄3的对象都要晋升。

    2. -XX:TargetSurvivorRatio这个参数来控制,默认为50%

    3. https://www.jianshu.com/p/989d3b06a49d

  9. 分配担保

    1. YGC期间 survivor区空间不够了,空间担保直接进入老年代(当在新生代无法分配内存的时候,把新生代的对象转移到老生代,然后把新对象放入腾空的新生代。)
    2. https://cloud.tencent.com/developer/article/1082730
  10. 对象何时进入老年代

    1. 超过-XX:MaxTenuringThreshold指定次数(YGC),Parallel Scavenge 15,CMS 6,G1 15

    2. 动态年龄 s1->s2超过50%

  11. 对象分配过程图

image

new一个对象,先进行线程栈上分配判断,如果能进行则进入栈上分配,然后结束

如果无法进行栈上分配则判断是否是大对象,如果是大对象则直接进入老年代分配,然后结束

如果不是大对象则进行TLAB判断,不过是否TLAB都是在eden区进行分配

然后执行gc,判断对象是否存活,没有存活则结束;如果对象存活则进入s1;再次执行gc再次判断对象是否存活,如果存活判断年龄是否大于指定参数,如果大于指定配置则进入老年代(cms 6,其他 15);如果年龄小于指定参数则进入s2;然后反复此步骤直接到结束

5. 垃圾回收器类型

image

红色虚线表示常见组合

  1. JDK诞生 Serial追随;为了提高效率诞生了PS;为了配合CMS诞生了PN,CMS是1.4版本后期引入,CMS是里程碑式的GC,它开启了并发回收的过程,但是CMS毛病较多,因此目前任何一个JDK版本默认是CMS;并发垃圾回收是因为无法忍受STW

  2. Serial,用于年轻代,会有STW,采用复制算法,使用单线程进行GC也就是串行回收(黄色箭头表示GC)

image

  1. Parallel Scavenge(PS),用于年轻代,会有STW,采用复制算法,使用多线程进行GC也就是并行回收(黄色箭头表示GC)

    image

  2. ParNew ,用于年轻代,会有STW,采用复制算法,使用多线程进行GC也就是并行回收(黄色箭头表示GC),与PS不同的是有增强功能配合CMS,例如它可以在CMS的并发阶段运行ParNew执行所需的同步

image

  1. SerialOld ,用于老年代,会有STW,采用标记-清除-压缩(Mark-Sweep-Compat,多次GC后才压缩是标记清除和标记压缩的结合。但不算在GC四大算法中),使用单线程进行GC也就是串行回收(黄色箭头表示GC)

image

  1. ParallelOld,用于老年代,会有STW,采用标记整理算法,使用多线程进行GC也就是并行回收(黄色箭头表示GC)

image

  1. ConcurrentMarkSwee,用于老年代,并发执行,垃圾回收和应用程序同时运行,降低STW的时间(200ms)。CMS问题比较多,所以现在没有一个版本默认是CMS,只能手工指定。CMS既然是MarkSweep,就一定会有碎片化的问题,碎片到达一定程度,CMS的老年代分配对象分配不下的时候,使用SerialOld 进行老年代回收。算法采用三色标记 + Incremental Update

  2. G1,设计目标在10ms内,算法采用三色标记 + SATB

  3. ZGC ,设计目标在1ms内,算法采用ColoredPointers + LoadBarrier

  4. Shenandoah,算法ColoredPointers + WriteBarrie

  5. Eplison,debug用的,不会做什么事的GC

  6. PS 和 PN区别:https://docs.oracle.com/en/java/javase/13/gctuning/ergonomics.html

  7. 垃圾收集器跟内存大小的关系:

    Serial 几十兆

    PS 上百兆 - 几个G

    CMS - 20G

    G1 - 上百G

    ZGC - 4T - 16T(JDK13)

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

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

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

    适用于小型程序。默认情况下不会是这种选项,HotSpot会根据计算及配置和JDK版本自动选择收集器

  • -XX:+UseParNewGC = ParNew + SerialOld

    这个组合已经很少用(在某些版本中已经废弃)

    https://stackoverflow.com/questions/34962257/why-remove-support-for-parnewserialold-anddefnewcms-in-the-future

  • -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

这篇关于GC基础知识的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!