2023-08-10  阅读(2)
原文作者:Ressmix 原文地址:https://www.tpvlog.com/article/85

一、简介

我们在 JVM垃圾回收机制一章中,简单介绍了JVM的垃圾回收机制,先来回顾下,系统运行时创建的对象优先在Java堆内存区域分配:

202308102126598921.png

然后新生代里的对象越来越多,当快满了的时候就会触发“Minor GC”,把新生代中的一些对象回收掉:

202308102127005722.png

那么这里就涉及一个问题: JVM如何知道要去回收哪些对象? 这其实就是JVM的对象存活判定机制,主要涉及两种算: 可行性分析算法引用计数算法

引用计数算法,是给对象添加一个引用计数器,每当有一个地方引用它时,计数器就加1,当引用失效时,计数器值就减1,任何时刻计数器为0的对象就是可以被回收的。

由于Java语言没有选用引用计数法来管理JVM内存,所以本文不赘述,而且引用计数法不能很好的解决循环引用的问题(Python采用的是引用计数法)。

二、可达性分析算法

可达性分析算法(GC Root Tracing ),其基本思路就是通过一系列的名为" GC Roots "的对象作为起始点,从这些起始点开始搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时(即从 GC Roots 到这个对象不可达),则证明此对象是不可用的,就可以被回收。

GC Roots包括:

  • Java虚拟机栈中的局部变量(指向着GC堆里的对象);
  • VM的一些静态数据结构里指向GC堆里的对象的引用,例如HotSpot VM里的Universe里有很多这样的引用;
  • 所有当前被加载的Java类(看情况);
  • Java类的运行时常量池里的引用类型常量;
  • String常量池(StringTable)里的引用。

可达性分析算法最难理解的就是该选取哪些对象作为GC Roots,我们通过两个示例来看下。

2.1 示例一

下面是最常见的一种情况:

    public class Kafka {
        public static void main(String[] args) {
            loadReplicasFromDisk();
        }
        public static void loadReplicasFromDisk(){
            ReplicaManager replicaManager = new ReplicaManager();
        }
    }

当执行到loadReplicasFromDisk()时,对应的JVM内存数据结构如下图:

202308102127016903.png

假如此时新生代的内存已经快满了,发生了“Minor GC”,那么JVM会分析ReplicaManager对象的可达性,发现它被“replicaManager”这个局部变量引用着,在JVM规范中, 局部变量是可以作为GC Roots的 ,所以就不会被回收。

2.2 示例二

另一种比较常见的情况,是下面这种样子:

    public class Kafka {
        public static ReplicaManager replicaManager = new ReplicaManager();
    }

对应的JVM内存数据结构如下图:

202308102127069794.png

假如此时新生代的内存已经快满了,发生了“Minor GC”,那么JVM会分析ReplicaManager对象的可达性,发现它被“replicaManager”这个方法区中的静态变量引用着,在JVM规范中, 静态变量是可以作为GC Roots的 ,所以就不会被回收。

三、Java引用类型

可达性分析与Java的引用类型有关联,为了更好的管理对象的内存,更好的进行垃圾回收,JVM团队扩展了引用类型,从最早的强引用类型增加到 强引用软引用弱引用虚引用 四个引用类型:

202308102127089035.png

3.1 强引用(StrongReference)

默认的对象都是强引用类型,如果JVM在对象存活判定时,通过GC Roots可达性分析结果为可达,表示引用类型仍然被引用着,这类对象始终不会被垃圾回收器回收。比如下面这段代码:

    public class Kafka {
        public static ReplicaManager replicaManager = new ReplicaManager();
    }

3.2 软引用(SoftReference)

在JVM内存充足的情况下,软引用是不会被GC回收的, 只有在JVM内存不足的情况下,才会被GC回收 。比如下面这段代码:

    public class Kafka {
        public static SoftReference<ReplicaManager> replicaManager = new SoftReference<ReplicaManager>(new ReplicaManager());
    }

适用场景: 网页缓存、图片缓存

3.3 弱引用(WeakReference)

不论当前JVM内存是否充足,都 只能存活到下一次垃圾收集之前 ,说的直白点,只要发生GC弱引用对象就会被回收,比如下面这段代码:

    public class Kafka {
        public static WeakReference<ReplicaManager> replicaManager = new WeakReference<ReplicaManager>(new ReplicaManager());
    }

ThreadlLocal中定义的ThreadLocalMap就使用到的弱引用。ThreadLocalMap的Entry,其Key就是一个弱引用对象,读者可以参考我的《Java多线程系列》

3.4 虚引用(PhantomReference)

虚引用,不会影响对象的生命周期,所持有的引用就跟没持有一样,随时都能被GC回收。在使用虚引用时,必须和 引用队列 关联使用。其使用场景是用来跟踪对象被垃圾回收器回收的活动。

在对象的垃圾回收过程中,如果GC发现一个对象还存在虚引用,则会把这个 虚引用加入到与之关联的引用队列 中。

程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象内存被回收之前采取必要的行动防止被回收。

四、finalize方法

大家理解完了GC Roots和引用类型的概念,基本就都知道了哪些对象可以被回收,哪些对象不可以被回收:

有GC Roots引用的对象不能回收,没有GC Roots引用的对象,如果是软引用或弱引用,可能会被回收。

真正的回收环节,待被回收的对象其实还有一次机会拯救自己,那就是对象的finalize()方法。我们通过一段代码示例来看下:

    public class ReplicaManager {
        public static ReplicaManager instance;
    
        @Override
        protected void finalize() throws Throwable {
            ReplicaManager.instance = this;
        }
    }

假如有一个ReplicaManager对象马上就要被回收了(此时已经没有GC Roots到达它的链路),此时GC会首先调用下该对象的finalize()方法,看看它是否找了一个新的GC Roots来引用自己,比如上述代码中,GC发现有个静态变量instance引用了该实例,那GC就不会去回收它。

finalize方法没事不要去重写,这都是GC内部的机制,平时也几乎不用。


Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文