五、自动内存管理机制---垃圾回收算法和垃圾收集器

本文深入探讨Java虚拟机(JVM)中的垃圾回收机制,涵盖对象生命周期、垃圾回收算法(如标记-清除、复制算法等)、垃圾收集器(如CMS、G1等)及其工作原理。同时,对比不同算法的优劣,帮助读者理解如何选择合适的垃圾收集策略。
本文参考资料:
深入理解JAVA虚拟机:JVM高级特性与最佳实践/周志明著
JAVA虚拟机精讲/高翔龙编著
 
如有描述不正确之处,欢迎大家指出
共勉

前序篇章有:

一、Java体系结构

二、字节码的编译原理

三、自动内存管理机制---Java内存区域


       这里我想先提出“自动化”的概念。在Java语言里面,所谓自动化可以理解为对内存的自动化管理,即内存可以“动态分配”与“内存回收"。这应该不难理解。

       针对于Java各个内存区域,它们内存的分配与回收的情况也不尽相同。比如程序计数器、虚拟机栈、本地方法栈这三个区域是线程私有的,随线程而生,也随线程而灭。栈中的栈帧随着方法的执行有条不紊地在栈中进行着进栈和出栈的操作。每一个栈帧分配多少内存基本是在类结构确定下来的时候就已经确定了的(忽略运行时JIT会做的某些优化)。因此以上3个区域的内存的分配及回收都具备确定性,我们在这里不过多的考虑回收的问题,因为当线程结束/方法结束时,内存随着就结束了。

       我们主要考虑堆和方法区。因为一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序运行时才能知道具体会创建哪些对象。这部分内存的分配和回收都是动态的。这部分也是我们接下来仔细学习的部分。


在正式开始学习垃圾回收之前,我们最好有下面的知识基础。

如果不想看这些预备知识的话,直接翻到下面看即可。垃圾回收算法和垃圾收集器都是很重要的点。

如何判断对象已死?

       我们知道,要回收内存之前,我们需要确定占用这块内存的对象已经不会再被使用了。

       那么,通过怎样的途径来做出判断呢?

  •   引用计数算法

         通过给对象添加一个引用计数器,当一个地方引用这个对象时,计数器就加一;当引用失效时,计数器就减一。

         当计数器的值为0时,就代表该对象不可能再被使用。

  •   根搜索算法

         通过命名一系列名为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链。当一个对象发哦GC Roots没有任何引用链相连,也就是说,当这个对象连不到GC Roots时,就可以确定此对象是不可用的。

        例如:下图中,Obj 4 、5、6都是不可再用的对象。也就是可回收的对象。


引用计数算法与根搜索算法:

       两种算法都是用来判断对象是否可回收的。

       引用计数算法有其缺点:不能解决循环引用问题。

       注意Java虚拟机没有采用引用计数算法来判断一个对象是否可回收,而是利用根搜索算法来判断。


显然,判断一个对象是否已经不可再用而需要被垃圾收集器回收时,离不开对象的引用。而我们所说的引用本质到底是什么呢?

在JDK1.2之后,Java对引用的概念做了扩充。将引用分为了四种,分别为:强引用、软引用、弱引用、虚引用。这四种引用的强度依次逐渐减弱。


 垃圾收集算法

垃圾收集算法是垃圾回收的方法论。

在垃圾收集方法中经常用到“标记”。在前面的根搜索遍历算法中不可达的对象,也不是一定就要回收的。因为要确定一个对象真正需要回收是需要经过至少两次标记过程的。如果对象在根搜索中发现某个对象和GC Roots不可达,那么就会第一次标记这个对象,之后再对第一次标记过的对象进行筛选。筛选的条件是当前对象是否有必要执行finalize()方法。下面画张图来表达筛选的过程:

 


 

接下来我们要了解的种垃圾收集算法有:

  • 标记-清除算法

       标记-清除算法包括“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,然后统一回收所有被标记的对象。

       此算法也有缺点:

              一、效率较低。因为标记过程和清除过程效率都不高。

              二、空间效率低。因为标记清除之后会产生大量不连续的内存碎片,空间碎片太多导致当有大的连续内存的需求时,无  法满足,而不得不继续触发一次垃圾收集动作。

 


  • 复制算法

     将内存分为相等大小的两部分,每次只使用其中的一部分,当这一部分用完时,直接将这部分的存活对象复制到另一半没使用的内存中,再将当前的内存全部回收。

     优点:不会产生内存碎片。因为每次回收都是一整片进行回收的。

                实现简单,运行高效。

     缺点:代价过高。因为使初始的内存大小直接变成了原来的一半。

      

上图是复制算法的回收过程。

商业中,很多虚拟机都采用复制算法来回收新生代。因为新生代基本都是朝生夕死。


  • 标记-整理算法

      标记-整理算法:可分为3阶段。

      第一阶段:标记。

      第二阶段:整理。

      第三阶段:清除。

      注意区分标记-整理算法和标记-清除算法。

      标记-清除只有俩阶段:标记、清除。标记完了之后直接将标记的地方清除掉,而没有其他操作。

      标记-整理有三个阶段:标记、整理、清除。标记完了之后,不直接进行清除回收操作,而是让所有存活对象都向一端移动,然后清理掉端边界以外的内存。

  • 分代收集算法

       首先根据对象的存活周期的不同将内存分为几块。

       一般是把Java堆分为新生代和老年代。

       新生代---朝生夕死  所以使用复制算法

       老年代---存活率高 所以使用标记-清理 标记-整理算法


垃圾收集器

垃圾收集器是垃圾回收的具体实现。

没有最好的垃圾收集器,一般虚拟机里面会实现多个垃圾收集器来配合使用。

下面是HotSpot JVM1.6的垃圾收集器:(图源优快云用户“一碗面”。这里为到达隧道:https://blog.youkuaiyun.com/qq_31156277/article/details/79951819

可以看到,这个虚拟机中实现了好多种收集器。

新生代的垃圾收集器:Serial、ParNew、Parallel Scavenge

老年代的垃圾收集器:CMS、Serial Old、Paeallel Old、G1


Serial收集器

Serial收集器是最基本的、历史最悠久的收集器。

它要求在进行垃圾收集操作时,停止其他工作线程。也就是Stop the world。也简称为STW。

优点:简单高效(与其他收集器的单线程相比),由于没有线程交互的开销,专心做垃圾收集,所以效率高。

缺点:有不小用户线程停顿。

综合:适用于Client模式下。因为用户的桌面应用场景中,分配给虚拟机管理的内存比较小,收集几十兆甚至一两百兆的新生代可以将停顿时间控制在几十毫秒,最多一百毫秒以内。只要不频繁发生,是可以接受的。


ParNew收集器

是Serial收集器的多线程版本。

和Serial基本相同,只不过在进行垃圾回收时,Serial是stw之后,只用一条线程来收集垃圾,而ParNew是用多条线程来收集垃圾。

在单CPU的环境下,由于Serial收集器不存在线程交互开销,所以Serial相对来说效果比较好。甚至在双CPU的情况下,ParNew也是比不过Serial收集器的效果。但是随着CPU的数量越来越多,ParNew收集器的优势也越来越明显。


Parallel Scavenge收集器

此收集器使用复制算法,是并行的多线程收集器,和ParNew很类似。

但是Parallel Scanvenge收集器的侧重点在于:达到一个可控制的吞吐量。所以也被称为“吞吐量优先”收集器。

高吞吐量可以最高效率的利用CPU时间,尽快地完成程序的运算任务。

高吞吐量---适合在后台运算而不需要太多交互的任务。

短停顿时间---适合需要与用户交互的任务。


Serial Old收集器

是Serial收集器的老年代版本。

Serial Old收集器是一个单线程收集器,使用“标记-整理算法”。


Parrallel Old收集器

是Parallel Scavenge收集器的老年代版本。

使用多线程和“标记-整理算法”。

有了Parallel Old收集器之后,终于有了名副其实的“吞吐量优先”组合:Parallel Scavenge收集器+Parallel Old收集器。


CMS收集器

CMS(Concurrent Mark Sweep)收集器是以获取最短停顿时间为目标的收集器。

使用“标记-清除算法”。

其运作过程相对前几种收集器稍微复杂一些,共有四个阶段:

  • 初始标记
  • 并发标记
  • 重新标记
  • 并发清除

其中,初始标记于并发标记仍需要“Stop the world”。

初始标记:只是标记一下GC Roots能关联到的对象,速度很快。

并发标记:GC Roots Tracing

重新标记:为了修正并发标记期间,因用户线程一起并发而导致标记产生变动的标记记录。

并发清除:清除需被回收的对象。

这四个阶段耗时的情况:

并发标记、并发清除 > 重新标记 > 初始标记

CMS收集器的缺点:

  • 对CPU资源很敏感
  • 无法处理浮动垃圾
  • 利用标记-清除算法,会产生大量内存碎片

G1收集器

利用标记-整理算法、可以非常精确地控制停顿。

G1收集器将整个Java堆划分为多个大小固定的独立区域,并且跟踪这些区域里面的垃圾堆积程度,在后台维护一个优先列表,每次根据允许的收集时间,优先回收垃圾最多的区域。

区域划分及有优先级的区域回收,保证了G1收集器在有限的时间内可以获得最高的收集效率。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值