被说烂了的Java垃圾回收算法,我带来了最“清新脱俗”的详细图解


被说烂了的Java垃圾回收算法,我带来了最“清新脱俗”的详细图解

文章插图
 
一、概况理解JAVA虚拟机垃圾回收机制的底层原理,是系统调优与线上问题排查的基础,也是一个高级Java程序员的基本功,本文就针对Java垃圾回收这一主题做一些整理与记录 。Java垃圾回收器的种类繁多,它们的设计要在吞吐量(内存空间)与实时性(用户线程中断)方面进行权衡,各个垃圾回收器的适应场景也不尽相同(如:桌面应用,web应用),因此,这里我们只讨论JDK8下的默认垃圾回收器,毕竟目前JDK8版本是业界的主流(占80%),并且我们只讨论堆内存空间的垃圾回收 。
JDK8下的默认垃圾回收器:UseParallelGC : Parallel (新生代)+ (老年代)堆内存回收机制
二、如何判断对象是否可回收?首先思考一个问题,内存堆中那么多对象,回收器要回收哪些对象?怎么判断出这些要回收的对象呢?因此对于垃圾回收,判断并标识对象是否可回收是第一步 。从理论层面来说,判断对象是否可回收一般两种方法 。
第一种、引用计数器算法:每当对象被引用一次计数器加1,对象失去引用计数器减1,计数器为0是就可以判断对象死亡了 。这种算法简单高效,但是对于循环引用或其他复杂情况,需要更多额外的开销,因此Java几乎不使用该算法 。
第二种、根搜索算法-可达性分析算法:所谓可达性分析是指,顺着GCRoots根一直向下搜索(用一个成语概括就是“顺藤摸瓜”),整个搜索的过程就构成了一条“引用链”,只要在引用链上的对象叫做可达,在引用链之外的(说明跟GCRoots没有任何关系)叫不可达,不可达的对象就可以判断为可回收的对象 。哪些对象可作为GCRoots对象呢? 包括如下:
  • 虚拟机栈帧上本地变量表中的引用对象(方法参数、局部变量、临时变量)
  • 方法区中的静态属性引用类型对象、常量引用对象
  • 本地方法栈中的引用对象(Native方法的引用对象)
  • Java虚拟机内部的引用对象,如异常对象、系统类加载器等
  • 所以被同步锁(synchronize)持有的对象
  • Java虚拟机内部情况的注册回调、本地缓存等
如果对虚拟机的内存布局与运行流程有所了解的话,这些作为GCRoots都很好理解,它们是程序运行时的源头,程序的正常运行必须依赖它们,而与这些源头没有任何关系的对象,即可视为可回收对象 。就好比“瓜从藤上掉下来了,那这瓜肯定也没有用了”

被说烂了的Java垃圾回收算法,我带来了最“清新脱俗”的详细图解

文章插图
?GCRoots可达性分析 不可达对象

被说烂了的Java垃圾回收算法,我带来了最“清新脱俗”的详细图解

文章插图
可达性分析
可达性分析从理论上很好理解,但在垃圾收集器具体运行时,要考虑的问题不知道要复杂多少倍,因为在可达性分析的同时,程序也是在并行运行着,整个内存堆的状态随着程序的运行是实时变化的,要实
现分析结果与内存状态的一致性,就必须要暂停用户线程,在一个快照去进行分析 。
三、垃圾回收算法可达性分析解决了判断对象是否可回收的问题,那么在垃圾回收时内存空间会发生哪些变化呢?这就是垃圾回收算法要讨论的问题,我们根据算法对内存采取的不同操作,可将垃圾回收算法分为3种,标记-清除算法、标记-复制算法、标记-整理算法 。
3.1 标记-清除算法根据名称就可以理解改算法分为两个阶段:首先标记出所有需要被回收的对象,然后对标记的对象进行统一清除,清空对象所占用的内存区域,下图展示了回收前与回收后内存区域的对比,红色的表示可回收对象,橙色表示不可回收对象,白色表示内存空白区域 。
被说烂了的Java垃圾回收算法,我带来了最“清新脱俗”的详细图解

文章插图
标记-清除算法 垃圾回收前后内存区域对比
标记-清除算法的两个缺点:
第一个:是执行效率不可控,试想一下如果堆中大部分的对象都可回收的,收集器要执行大量的标记、收集操作 。
第二个:产生了许多内存碎片,通过回收后的内存状态图可以知道,被回收后的区域内存并不是连续的,当有大对象要分配而找不到满足大小的空间时,要触发下一次垃圾收集 。
 
3.2 标记-复制算法针对标记-清除算法执行效率与内存碎片的缺点,计算机科学家又提出了一种“半复制区域”的算法 。


推荐阅读