垃圾回收具体技术

本节将对JVM中的垃圾回收技术进行具体的介绍。

2.1 垃圾识别

垃圾回收的第一步就是识别哪些是垃圾,本节就来介绍垃圾识别有哪些技术。

2.1.1 引用计数法

引用计数法给对象添加一个引用计数器,每当有一个地方引用该对象,对象的计数器就加 1,当引用失效,计数器就减 1,任何时候计数器为 0 的对象就是不可能再被使用的。这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来进行垃圾识别,其最主要的原因是它很难解决对象之间相互循环引用的问题。所谓对象之间的相互引用问题,如下面代码所示:除了对象 objA 和 objB 相互引用着对方之外,再没有其他地方引用它们。但是他们因为互相引用对方,导致它们的引用计数器都不为 0,于是引用计数算法无法通知 GC 回收器回收他们。

public class ReferenceCountingGc {
    Object instance = null;
    public static void main(String[] args) {
        ReferenceCountingGc objA = new ReferenceCountingGc();
        ReferenceCountingGc objB = new ReferenceCountingGc();
        objA.instance = objB;
        objB.instance = objA;
        objA = null;
        objB = null;

    }
}

 

2.1.2 可达性分析法

可达性分析算法通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。

图片说明

在 Java 中,可作为 GC Root 的对象通常包括下面几种:

  • 虚拟机栈中(栈帧中的本地变量表)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法中栈中 JNI 引用的对象
  • 所有被同步锁synchronized持有的对象
  • jvm内部的引用对象,如基本数据类型对应的Class对象,一些常驻的异常对象(如: NullPointerException、OutOfMemoryError)
  • 反映jvm内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等
  • 根据用户所选用的垃圾收集器和当前回收的内存区域不同,其他临时性对象
     

即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize方法。当对象没有覆盖 finalize 方法,或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。被判定为需要执行的对象将会被放在一个队列 F-Queue 中,稍后由一个虚拟机自动建立的、低优先级的 Finalizer 线程去执行它。如果在 finalize方法中出现死循环,则可能导致内存回收系统崩溃。finalize方法使得对象有一次逃离死亡的机会,只要这个对象与引用链上的任何一个对象建立关联即可,否则就会被真的回收。任何对象的finalize方法都只会被系统自动调用一次。

可达性分析法就是从GC Root开始进行扫描的,并且知道栈帧中的引用类型是GC Roots集合中的一个重要部分,因此如何获取到这些引用类型数据就是GC的重要一步了。根据如何获取,GC可分为如下三种类型:

  1. 保守式GC。如果JVM不记录数据的类型,那么它就无法区分内存里某个位置上的数据到底应该解读为引用类型还是其他类型,这种GC就是“保守式GC(conservative GC)”。因此只能靠一些条件来猜测数据是否是指向GC堆中的指针。比如在栈上扫描的时候根据所在地址是否在GC堆的上下界之内,是否字节对齐等手段来判断这个是不是指向 GC 堆中的指针。
  2. 半保守式GC。JVM不在栈上记录类型信息,而在对象上记录类型信息,这种GC方式就是半保守式GC。这种方式从根扫描的话还是一样,得靠猜测,但是得到堆内对象了之后,就能准确知晓对象所包含的信息了,因此之后扫描 都是准确的。
  3. 准确式GC。JVM能够判断出所有位置上的数据是不是指向GC堆里的引用,这种方式就是准确式GC。
     

2.2 JVM中的引用

无论是在引用计数法还是可达性分析法中都有一个很重要的概念“引用”,为了加深理解,本节对JVM中的引用进行详细的介绍。

引用分为强引用、软引用、弱引用、虚引用四种(引用强度逐渐减弱):

  • 强引用,常见的引用,垃圾回收器绝不会回收它。强引用就是我们平常用的类似于“Object obj = new Object()”的引用。当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题

  • 软引用。Java提供了SoftReference类来实现软引用。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。软引用可以加速 JVM 对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。软引用适合缓存的场景。Guava Cache是Google开源的本地缓存实现库,该类库支持基于引用的回收,CacheBuilder.softValues()使用软引用存储值,目的就是在内存不够是进行回收,防止内存溢出。

  • 弱引用。Java提供了WeakReference类来实现软引用。只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。ThreadLocalMap类中的Entry的Key即ThreadLocal对象是采用弱引用的,源码如下。假如使用强引用,当ThreadLocal不再使用需要回收时,发现某个线程中ThreadLocalMap存在该ThreadLocal的强引用,无法回收,造成内存泄漏。因此,使用弱引用可以防止长期存在的线程(通常使用了线程池)导致ThreadLocal无法回收造成内存泄漏。

    static class ThreadLocalMap {
    
          /**
           * The entries in this hash map extend WeakReference, using
           * its main ref field as the key (which is always a
           * ThreadLocal object).  Note that null keys (i.e. entry.get()
           * == null) mean that the key is no longer referenced, so the
           * entry can be expunged from table.  Such entries are referred to
           * as "stale entries" in the code that follows.
           */
          static class Entry extends WeakReference<ThreadLocal<?>> {
              /** The value associated with this ThreadLocal. */
              Object value;
    
              Entry(ThreadLocal<?> k, Object v) {
                  super(k);
                  value = v;
              }
          }
  • 虚引用。虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。虚引用唯一的用处在于对象被回收器回收时收到一个系统通知。FileCleaningTracker是一个文件清理的跟踪器,它的实现不是简单的通过定时器来定时清理文件,而是使用跟踪GC来清理文件。FileCleaningTracker就是利用了虚引用,当虚引用的对象被清理时执行相应的业务动作。
     

2.3 垃圾回收算法

本小节将介绍具体的垃圾回收算法。

2.3.1 标记清除算法

该算法分为“标记”和“清除”阶段:首先标记出所有存活的对象,在标记完成后统一回收所有未被标记的对象。它是最基础的收集算法,后续的算法都是对其不足进行改进得到。这种垃圾收集算带来两个明显的问题:

  • 效率问题。当在执行标记和清除两个动作时,用户程序是停止运行的,这会导致用户体验非常差,尤其对于交互式的应用程序来说简直是无法接受。试想一下,如果你正在和一个网站进行交互,这个网站十几分钟就需要停机几分钟,这能忍受吗?
  • 空间问题(标记清除后会产生大量不连续的碎片)。如下图所示,垃圾对象无规律的分布在内存的各个地方,回收后内存的布局自然会乱七八糟。碎片太多会导致如果以后程序运行过程中需要分配大对象,将无法找到足够大的连续空间,所以会被迫再次进行垃圾回收。

图片说明

2.3.2 复制算法

为了解决内存碎片化的问题,“复制”收集算法出现了。它将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

Java面试必问JVM考点精讲 文章被收录于专栏

“挨踢”行业行情日益严峻,企业招聘的门槛也随之越来越高,大厂hc少之又少。 庞大的知识体系下,不知道学什么、怎么学? 面试高频考点是什么、怎么回答才能得到面试官的青睐? 作为后端求职者,在Java的道路上越走越宽。 本专刊则针对Java面试考点上,精讲JVM知识点,为大家的大厂求职路保驾护航! 针对如今校招痛点,深入详解JVM知识考点,列出高频真题并详细解答!探索JVM精髓!

全部评论

相关推荐

真tmd的恶心,1.面试开始先说我讲简历讲得不好,要怎样讲怎样讲,先讲背景,再讲技术,然后再讲提升多少多少,一顿说教。2.接着讲项目,我先把背景讲完,开始讲重点,面试官立即打断说讲一下重点,无语。3.接着聊到了项目的对比学习的正样本采样,说我正样本采样是错的,我解释了十几分钟,还是说我错的,我在上一家实习用这个方法能work,并经过市场的检验,并且是顶会论文的复现,再怎么不对也不可能是错的。4.面试官,说都没说面试结束就退出会议,把面试者晾在会议里面,丝毫不尊重面试者难受的点:1.一开始是讲得不好是欣然接受的,毕竟是学习。2.我按照面试官的要求,先讲背景,再讲技术。当我讲完背景再讲技术的时候(甚至已经开始蹦出了几个技术名词),凭什么打断我说讲重点,是不能听出人家重点开始了?这也能理解,每个人都有犯错,我也没放心上。3.我自己做过的项目,我了解得肯定比他多,他这样贬低我做过的项目,说我的工作是错误的,作为一个技术人员,我是完全不能接受的,因此我就和他解释,但无论怎么解释都说我错。凭什么,作为面试官自己不了解相关技术,别人用这个方式work,凭什么还认为这个方法是错的,不接受面试者的解释。4.这个无可厚非,作为面试官,不打招呼就退出会议,把面试者晾着,本身就是有问题。综上所述,我现在不觉得第一第二点也是我的问题,面试官有很大的问题,就是专门恶心人的,总结面试官说教,不尊重面试者,打击面试者,不接受好的面试者,技术一般的守旧固执分子。有这种人部门有这种人怎么发展啊。最后去查了一下,岗位关闭了。也有可能是招到人了来恶心人的,但是也很cs
牛客20646354...:招黑奴啊,算法工程师一天200?
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务