JVM系列之垃圾回收机制(Garbage Collect)

举报
yd_273762914 发表于 2021/06/23 00:05:26 2021/06/23
【摘要】 JVM系列之垃圾回收机制(Garbage Collect) 1、前言介绍 在前面章节的学习中,我们知道了java虚拟机的运行时数据区和类加载机制,了解了在堆内存中是有垃圾回收的,比如young区的Minor GC,Old区的Major GC,young区和old区的full GC。 对于一个内存中的对象,怎么确定它需要回收的?怎么样对它进行回收? 2、如何确定一...

JVM系列之垃圾回收机制(Garbage Collect)

1、前言介绍

前面章节的学习中,我们知道了java虚拟机的运行时数据区和类加载机制,了解了在堆内存中是有垃圾回收的,比如young区的Minor GC,Old区的Major GC,young区和old区的full GC。

对于一个内存中的对象,怎么确定它需要回收的?怎么样对它进行回收?

2、如何确定一个对象需要回收?

2.1、引用计数法

对于引用计数法而言,只要应用程序中持有对该对象的引用,则这个对象不需要回收,如果这个对象没有任何指针对其引用,则这个对象需要回收。

弊端:如果对象A和B之间相互持有引用,会导致永远不会被回收

写个例子进行验证:

public class TestGc { static class A{ public B b; } static class B { public A a; } public static void testGc() { A a = new A(); B b = new B(); a.b = b; b.a = a; a = null; b = null; // 强制进行gc回收 System.gc(); } public static void main(String[] args) { testGc(); }
}


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

添加java虚拟机参数-XX:+PrintGC,在控制台打印出gc日志

从日志可以看出,在jdk8里还是有进行回收的,说明jdk8默认的回收机制不是基于“引用计数法”的

[GC (System.gc())  4301K->1033K(117760K), 0.0024682 secs]
[Full GC (System.gc())  1033K->990K(117760K), 0.0170595 secs]

  
 
  • 1
  • 2

2.2、可达性分析

通过GC Root的对象,开始向下寻找,看某个对象是否可达,能查找到就是可达
在这里插入图片描述

在Java技术体系里面,固定可作为GC Roots的对象有:

  • 虚拟机栈引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(Native方法)引用的对象
  • ·所有被同步锁(synchronized)持有的对象
  • 类加载器、Thread、基本数据类型对应的Class对象、常驻的异常对象
  • 等等

3、什么时候会垃圾回收?

  • (1)、当Eden区或者S区不够用时
  • (2)、old区空间不够用时
  • (3)、方法区空间不够用时
  • (4)、System.gc() 手动回收(通知回收,什么时候回收由jvm决定,生产环境不建议使用,因为gc消耗的资源比较大)

4、 垃圾收集算法

4.1、标记-清除(Mark-Sweep)

  • 标记Mark
    找出内存中需要回收的对象,并且将它们标记出来
    引用官网图片:
    在这里插入图片描述

  • 清除(Sweep)
    清除掉被标记需要回收的对象,释放出对应的内存空间
    在这里插入图片描述

4.2、标记-复制(Mark-Copying)

将不需要回收的对象,如图Referenced对象复制到S0,然后清理Eden,当然Young区的垃圾回收,还有没那么简单,这里不详细描述(为什么要两个S区的原因可以找上一章学习)
在这里插入图片描述

4.3、标记-整理(Mark-Compact)

同样需要标记的过程:
在这里插入图片描述
让所有Referenced对象都向一端移动,清理掉边界意外的内存。
在这里插入图片描述

5、 分代垃圾收集过程

在前面的学习中,我们知道了堆是jvm一个很重要的部分,堆可以分为young区(“新生代”)和old区(“老年代”),young区再细分为Eden区、S0区(From区)、S1区(To区),然后对象是怎么进行分代分配收集的,然后按照官网描述走一遍流程,图来自官网

  • 1、任何新对象最开始都被分配到 eden 空间。两个幸存者空间一开始都是空的,图来自官网
    在这里插入图片描述

  • 2、伊甸园空间填满时,会触发一个次要的垃圾收集
    在这里插入图片描述

  • 3、引用的对象会被移到S0区,然后清理Eden区,未引用的对象被清理

在这里插入图片描述

  • 4、在下一次的小GC中,Eden区引用的对象同样会移到幸存区,不过这次不是S0(From)区,而是S1(To)区。同时原来S0(From)区中的引用对象达到一个阈值后,也会被移到S1区,当所有符合条件的引用对象都被移到S1时,就会触发GC,清理Eden区、S0区的对象

在这里插入图片描述

  • 5、在接下来的下一个次要(minor )GC,会重复同样的过程。不过这一次,幸存区被换了,这次换成S0区,Eden区引用的对象和S1中的引用对象被移到S0区,然后清理Eden区和S1区
    在这里插入图片描述

  • 6、前面就是young区的minor GC大概过程,当对象达到一定的年龄阈值(本例中为8)时,它们会从“年轻代“提升到“老年代“。

在这里插入图片描述

  • 7、 随着次要 GC 的继续发生,对象将继续被提升到老年代空间
    在这里插入图片描述

  • 8、如果old区空间满了,将在old区执行一次major GC,清理并压缩该空间
    在这里插入图片描述

6、 垃圾收集器

6.1、垃圾收集器详细介绍

垃圾收集算法是方法论,垃圾收集器就是内存回收的具体实现,按照并行多线程、作用 范围(作用于“新生代”还是“老年代”),可以细分为各类的垃圾收集器

  • young Generation Collection:

    • Serial
      Serial收集器是最基本,发展历史最早的收集器,在jdk1.3.1之前是java虚拟机唯一的选择,它是一种单线程的收集器,采用复制算法
    • ParNew
      ParNew是一种多线程版本的收集器,也是采用复制算法的收集器,可以理解为Serial的多线程版本。
    • Parallel Scavenge
      Parallel Scavenge 也是一种复制类型的收集器,支持多线程并发,看起来和ParNew有点像,不过Parallel Scanvenge更关注系统的吞吐量
  • Old Generation Collection

    • Serial Old
      Serial Old收集器是Serial收集器的老年代版本,也是一个单线程收集器,采用的收集算法是“标记-整理”,“mark-sweep-compact”,运行过程和Serial一样
    • CMS
      Concurrent Mark Sweep(CMS),是一种以获取 最短回收停顿时间 为目标的收集器,采用的是标记-清除算法官方文档

    整个过程分为4:

    • (1)、初始标记(CMS initial mark):标记GC roots直接关联对象,不需要Tracing,速度是很快的
    • (2)、并发标记(CMS concurrent mark):这个过程会进行GC Roots tracing
    • (3)、重新标记(CMS remark):重新标记并发标记中因用户程序改变的内容
    • (4)、并发清楚(CMS concurrent sweep):这个过程会清理不可达的对象,回收内存空间(不过这个过程会产生新垃圾,留着下次清理,这个被称之为浮动垃圾)
      注意:总体上来说,CMS收集器的内存回收过程是与用户线程一起并发地执行的,因为整体过程,并发标记和并发清除,收集器线程可以与用户线程一起工作
      在这里插入图片描述
    • Parallel Old
      Parallel Old收集器是Parallel Scavenge收集器的老年代版本,支持多线程,使用“标记-整理算法”进行垃圾回收,也是更加关注系统的吞吐量
  • G1
    Garbage-First (G1) 收集器是一种服务器式垃圾收集器,适用于具有大内存的多处理器机器。

G1收集器,Java堆的内存布局和其它收集器有很大差别,它将整个java堆划分为多个大小相等的独立区域(Region),虽然还保留着“新生代”和“老年代”的概念,不过“新生代”和“老年代”不再是以前的设计模型,而是很大Region区域的集合。所谓Garbage-Frist,其实就是优先回收垃圾最多的Region区域

引用官网的图例:
在这里插入图片描述
每个Region大小都是一样的,可以是1M到32M之间的数值,但是必须保证是2的n次幂,如果对象太大,一个Region放不下,超过了Region大小的50%,就会直接放到H中

注意:设置Region大小:-XX:G1HeapRegionSize=M

G1收集器收集过程:

  • 初始标记(Initial Marking) 标记以下GC Roots能够关联的对象,并且修改TAMS的值,需要暂停用户线程
  • 并发标记(Concurrent Marking) 从GC Roots进行可达性分析,找出存活的对象,与用户线程并发执行
  • 最终标记(Final Marking) 修正在并发标记阶段因为用户程序的并发执行导致变动的数据,需暂停用户线程
  • 筛选回收(Live Data Counting and Evacuation) 对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间制定回收计划

在这里插入图片描述

  • ZGC
    The Z Garbage Collector (ZGC)是一种可扩展的低延迟垃圾收集器。是自从JDk11才有的,不管是物理上还是逻辑上,ZGC中已经不存在新老年代的概念了。它的数据结构会分为一个个page,当进行GC操作时会对page进行压缩,因此没有碎片问题。只能在64位的linux上使用,目前用得还是比较少的

注意:开启命令,XX:+UnlockExperimentalVMOptions -XX:+UseZGC。

6.2、垃圾收集器分类

比较详细地学习了各类垃圾收集器之后,需要进行归类总结一下知识点,按照垃圾收集器的作用范围,作用于“新生代”还是“老年代”可以分为如图所示:

引用官网webfolder PDF的图片:
在这里插入图片描述

只作用于“新生代”的有Serial、ParNew、ParalleScavenge;只作用于“老年代”的有Serial Old、CMS、Parallel Old;同时可以作用于“新生代”和“老年代”的有G1

在这里插入图片描述

按照是否支持并发的,可以分为串行收集器和并行收集器

  • 串行收集器
    Serial和Serial Old,只有一个垃圾回收线程执行,执行期间用户线程暂停,适用于内存比较小的嵌入式设备

  • 并行收集器[吞吐量优先]
    Parallel Scanvenge、Parallel old,多个线程执行,此时用户线程仍然处于等待状态。

  • 并行收集器[停顿时间优先]
    CMS、G1,用户线程和垃圾收集线程同时执行,但并不一定是并行的,可能是交替执行的,垃圾收集线程在执行的时候不会停顿用户线程的运行

在这里插入图片描述

文章来源: smilenicky.blog.csdn.net,作者:smileNicky,版权归原作者所有,如需转载,请联系作者。

原文链接:smilenicky.blog.csdn.net/article/details/117660143

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。