JVM堆内存里面的垃圾回收

2023年 8月 13日 44.7k 0

1、如何确定一个对象是垃圾

堆内存中有垃圾回收,比如Young区的Minor GC,Old区的Major GC,Young区和Old区
的Full GC。但是对于一个对象而言,怎么确定它是垃圾?是否需要被回收?怎样对它进行回收?等等这些问
题我们还需要详细探索。

要想进行垃圾回收,得先知道什么样的对象是垃圾。

1.1 引用计数法

对于某个对象而言,只要应用程序中持有该对象的引用,就说明该对象不是垃圾,如果一个对象没有任何指针对其引用,它就是垃圾。如下图所示obj1有三个引用,obj2有两个引用,而obj3没有引用,所以obj3就是垃圾对象。
image.png
但是这种计数法也存在问题,如果堆中两个对象互相引用了,就会导致这两个对象永远无法被回收,如下图ob3和obj4所示。
image.png

1.2 可达性分析

通过GC Root的对象,开始向下寻找,看某个对象是否可达(也就是看这个对象被引用的顶级对象是否是GC Root对象)

能作为GC Root:类加载器、Thread、虚拟机栈的本地变量表、static成员、常量引用、本地方法栈的变量等。
例如下图的obj7和obj8就是垃圾对象。

image.png

2、垃圾收集算法

已经能够确定一个对象为垃圾之后,接下来要考虑的就是回收,怎么回收呢?得要有对应的算法。
下面介绍常见的垃圾回收算法。

2.1 标记-清除(Mark-Sweep)

  • 标记:找出内存中需要回收的对象,并且把它们标记出来(此时堆中所有的对象都会被扫描一遍,从而才能确定需要回收的对象,比较耗时)
    image.png
  • 清除:清除掉被标记需要回收的对象,释放出对应的内存空间
    image.png

缺点

标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

(1)标记和清除两个过程都比较耗时,效率不高

(2)会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

2.2 标记-复制(Mark-Copying)

将内存划分为两块相等的区域,每次只使用其中一半,如下图所示:
image.png
当其中一块内存使用完了,就将还存活的对象复制到另外一块上面,然后把已经使用过的内存空间一次清除掉。
image.png
缺点:堆内存每次只能使用一半,空间利用率降低。

2.3 标记-整理(Mark-Compact)

标记过程仍然与"标记-清除"算法一样,但是后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。(其实上述过程相对"复制算法"来讲,少了一个"保留区")
image.png
让所有存活的对象都向一端移动,清理掉边界意外的内存。
image.png
缺点:效率偏低。

3、Java内存的管理模型

3.1 分代模型

堆内存区分为两大块,一个是Old区,一个是Young区。
Young区分为两大块,一个是Survivor区(S0+S1),一块是Eden区,S0和S1一样大,也可以叫From和To。
image.png
Young区:复制算法(对象在被分配之后,可能生命周期比较短,Young区复制效率比较高)。虽然是采用的是复制算法,但为了是内存利用率更高,Eden区和s0,s1的比例是8:1:1。比如Eden区中有个对象obj1不是垃圾对象,经过垃圾回收之后,被复制到s0区,然后Eden区清空,而后续的obj1就会在s0和s1来回复制,直到最后进入到old区。

Old区:标记清除或标记整理(Old区对象存活时间比较长,复制来复制去没必要,不如做个标记再清理

3.2 分区模型

将堆空间划分程连续的不同小区间,每一个小区间都独立使用,独立回收
image.png

4、垃圾收集器

如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。
image.png
左边6种用于分代模型,右边的用于分区模型

4.1 Serial

Serial收集器是最基本、发展历史最悠久的收集器,曾经(在JDK1.3.1之前)是虚拟机新生代收集的唯一选择。

它是一种单线程收集器,不仅仅意味着它只会使用一个CPU或者一条收集线程去完成垃圾收集工作,更重要的是其在进行垃圾收集的时候需要暂停其他线程(比如业务线程)。

image.png

  • 优点:简单高效,拥有很高的单线程收集效率
  • 缺点:收集过程需要暂停所有线程
  • 算法:复制算法
  • 适用范围:新生代
  • 应用:Client模式下的默认新生代收集器

4.2 Serial Old

Serial Old收集器是Serial收集器的老年代版本,也是一个单线程收集器,不同的是采用"标记-整理算法",运行过程和Serial收集器一样。

image.png

4.3 ParNew

可以把这个收集器理解为Serial收集器的多线程版本。

image.png

  • 优点:在多CPU时,比Serial效率高。
  • 缺点:收集过程暂停所有应用程序线程,单CPU时比Serial效率差。
  • 算法:复制算法
  • 适用范围:新生代
  • 应用:运行在Server模式下的虚拟机中首选的新生代收集器

4.4 Parallel Scavenge

Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集器,看上去和ParNew一样,但是Parallel Scanvenge更关注系统的吞吐量。

4.5 Parallel Old

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多线程和标记-整理算法进行垃圾回收,也是更加关注系统的吞吐量。

4.6 CMS

官网 : docs.oracle.com/javase/8/do…

上述的垃圾收集器都存在业务线程终止的情况,(垃圾回收线程和业务线程串行),STW较长。

CMS(Concurrent Mark Sweep)收集器是一种以获取 最短回收停顿时间 为目标的收集器。
采用的是"标记-清除算法",整个过程分为4步

  • 初始标记 CMS initial mark 标记GC Roots直接关联对象,不用Tracing,速度很快
  • 并发标记 CMS concurrent mark 进行GC Roots Tracing
  • 重新标记 CMS remark 修改并发标记因用户程序变动的内容
  • 并发清除 CMS concurrent sweep 清除不可达对象回收空间,同时有新垃圾产生,留着下次清理称为
  • 浮动垃圾
    由于整个过程中,并发标记和并发清除,收集器线程可以与用户线程一起工作,所以总体上来说,CMS收集器的内存回收过程是与用户线程一起并发地执行的。

image.png

  • 优点:并发收集、低停顿
  • 缺点:产生大量空间碎片、并发阶段会降低吞吐量

从CMS往后的收集器都是垃圾回收线程和业务线程一起在执行,就会导致很多问题,比如一个未被引用的对象(即垃圾对象),垃圾收回线程标记为垃圾对象,但未执行回收操作,然后业务线程介入,引用了这个线程,后面垃圾回收线程回收之后,就会导致这个对象为null,业务线程就会有问题。
解决方案参考:blog.csdn.net/weixin_3955…

此文章参考了马士兵严镇涛老师的JVM课程和课件。

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论