面试必问:JVM 如何确定死亡对象?

简介: 面试必问:JVM 如何确定死亡对象?

在 JVM 中,有两个非常重要的知识点,一个是 JVM 的内存布局(JVM 运行时的数据区域),另一个就是垃圾回收。而垃圾回收中又有两个重要的知识点,一个是如何确定 JVM 中的垃圾对象,另一个是使用不同的垃圾收集器进行垃圾回收。而本篇要讨论的是前者,后面的内容咱们下一篇再聊。

垃圾对象的判定有两种常用的算法:引用计数器算法和可达性分析算法。

1.引用计数器算法

引用计数算法(Reference Counting) 属于垃圾收集器的早期实现算法了,它指的是在创建对象时关联一个与之相对应的计数器,当此对象被使用时加 1,相反销毁时 -1。当此计数器为 0 时,则表示此对象未使用,可以被垃圾收集器回收。

引用计数算法的优缺点很明显,其优点是垃圾回收比较及时,实时性比较高,只要对象计数器为 0,则可以直接进行回收操作;而缺点是无法解决循环引用的问题,比如以下代码:

public class RefCounterTest {
    // 对象 A
    static class RefObjectA {
        private RefObjectB refObjectB;

        public void setRefObjectB(RefObjectB refObjectB) {
            this.refObjectB = refObjectB;
        }
    }
    // 对象 B
    static class RefObjectB {
        private RefObjectA refObjectA;

        public void setRefObjectA(RefObjectA refObjectA) {
            this.refObjectA = refObjectA;
        }
    }
    // 测试代码
    public static void main(String[] args) {
        RefObjectA objectA = new RefObjectA();
        RefObjectB objectB = new RefObjectB();
        objectA.setRefObjectB(objectB);
        objectB.setRefObjectA(objectA);
        objectA = null;
        objectB = null;
    }
}

如以上代码所示,即使是将 main 方法中的 objectA 和 objectB 都设置为 null,也就是这两个对象都彻底不使用了,但是因为二者存在相互引用的关系,所以它们所对应的对象计数器不为 0,这样循环引用导致垃圾数据无法被清除的事件就产生了。

2.可达性分析算法

可达性分析算法(Reachability Analysis) 是目前主流虚拟机中,使用最广泛的判断垃圾对象的实现算法,它指的是从对象的起点(GC Roots)开始向下搜索,如果对象到 GC Roots 没有任何引用链相连时,也就是说此对象到 GC Roots 不可达时,则表示此对象可以被垃圾回收器所回收,如下图所示:
image.png
在 Java 语言中,可作为根节点(GC Roots)的对象有以下 4 类:

  1. Java 虚拟机栈中的引用对象,也就是 Java 虚拟机栈帧中,本地变量表所存储的(引用)对象。在 Java 虚拟机栈帧中存储的对象都是将来执行时,要使用的对象,所以和引用对象相关的对象都不能被回收;
  2. 本地方法栈中的引用对象和 Java 虚拟机栈中的引用对象类似,也不能被回收;
  3. 方法区中类静态属性引用的对象也可以作为 GC Roots;
  4. 方法区中常量引用的对象也可以作为 GC Roots。因为常量是保存在常量池中的,属于全局可使用的对象,所以也能作为 GC Roots。

3.有关“引用”

不管是引用计数法还是可达性分析算法都与对象的“引用”有关,这说明对象的引用决定了对象的生死,而 Java 中的引用也比较复杂,它从 JDK 1.2 之后,(引用)分成了以下 4 种类型:

  • 强引用:在代码中普遍存在的,类似 Object obj = new Object() 这类引用,只要强引用还在,垃圾收集器永远不会回收掉被引用的对象
  • 软引用:是一种相对强引用弱化一些的引用,可以让对象豁免一些垃圾收集,只有当 JVM 认为内存不足时,才会去试图回收软引用指向的对象,JVM 会确保在抛出 OutOfMemoryError 之前,清理软引用指向的对象
  • 弱引用:非必需对象,但它的强度比软引用更弱,被弱引用关联的对象只能生存到下一次垃圾收集发生之前
  • 虚引用:也称为幽灵引用或幻影引用,是最弱的一种引用关系,无法通过虚引用来获取一个对象实例,为对象设置虚引用的目的只有一个,就是当着个对象被收集器回收时收到一条系统通知

总结

垃圾对象的判定有两种常用的算法:引用计数器算法和可达性分析算法。其中引用计数器算法实现简单、运行高效,但是存在循环引用的问题,所以主流的虚拟机使用的都是可达性分析算法,可达性分析算法是从对象的根节点 GC Roots 向下搜索,如果根节点相连就是正常的对象,否则为垃圾对象可以被垃圾回收器回收。

本文已收录到 Gitee 开源仓库《Java 面试指南》,其中包含的内容有:Redis、JVM、并发、并发、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、设计模式、消息队列等模块。Java 面试有它就够了: 超全 Java 常见面试题,持续更新...
相关文章
|
13天前
|
安全 Java 应用服务中间件
JVM常见面试题(三):类加载器,双亲委派模型,类装载的执行过程
什么是类加载器,类加载器有哪些;什么是双亲委派模型,JVM为什么采用双亲委派机制,打破双亲委派机制;类装载的执行过程
JVM常见面试题(三):类加载器,双亲委派模型,类装载的执行过程
|
18天前
|
存储 缓存 监控
【Java面试题汇总】JVM篇(2023版)
JVM内存模型、双亲委派模型、类加载机制、内存溢出、垃圾回收机制、内存泄漏、垃圾回收流程、垃圾回收器、G1、CMS、JVM调优
【Java面试题汇总】JVM篇(2023版)
|
2月前
|
存储 安全 Java
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程,JDK、JRE、JVM关系;程序计数器,堆,虚拟机栈,堆栈的区别是什么,方法区,直接内存
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程是什么,JDK、JRE、JVM的联系与区别;什么是程序计数器,堆,虚拟机栈,栈内存溢出,堆栈的区别是什么,方法区,直接内存
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程,JDK、JRE、JVM关系;程序计数器,堆,虚拟机栈,堆栈的区别是什么,方法区,直接内存
|
2月前
|
JavaScript
【Vue面试题九】、Vue中给对象添加新属性界面不刷新?
这篇文章讨论了Vue中给对象动态添加新属性时界面不刷新的问题,并提供了三种解决方案:使用`Vue.set()`方法来确保新属性是响应式的并触发视图更新,使用`Object.assign()`创建新对象以合并新属性,以及作为最后手段的`$forceUpdate()`进行强制刷新。文章还简要分析了Vue 2和Vue 3在数据响应式实现上的差异。
|
3月前
|
Java 编译器 程序员
JVM常见面试题(一):JVM是什么、由哪些部分组成、运行流程是什么,JDK、JRE、JVM的联系与区别
JVM常见面试题(一):JVM是什么、由哪些部分组成、运行流程是什么,JDK、JRE、JVM的联系与区别
JVM常见面试题(一):JVM是什么、由哪些部分组成、运行流程是什么,JDK、JRE、JVM的联系与区别
|
2月前
|
JavaScript
【Vue面试题八】、为什么data属性是一个函数而不是一个对象?
这篇文章解释了为什么在Vue中组件的`data`属性必须是一个函数而不是一个对象。原因在于组件可能会有多个实例,如果`data`是一个对象,那么这些实例将会共享同一个`data`对象,导致数据污染。而当`data`是一个函数时,每次创建组件实例都会返回一个新的`data`对象,从而确保了数据的隔离。文章通过示例和源码分析,展示了Vue初始化`data`的过程和组件选项合并的原理,最终得出结论:根实例的`data`可以是对象或函数,而组件实例的`data`必须为函数。
【Vue面试题八】、为什么data属性是一个函数而不是一个对象?
|
2月前
|
缓存 算法 Java
这些年背过的面试题——JVM篇
本文是技术人面试系列JVM篇,面试中关于JVM都需要了解哪些基础?一文带你详细了解,欢迎收藏!
|
3月前
|
存储 监控 算法
(六)JVM成神路之GC基础篇:对象存活判定算法、GC算法、STW、GC种类详解
经过前面五个章节的分析后,对于JVM的大部分子系统都已阐述完毕,在本文中则开始对JVM的GC子系统进行全面阐述,GC机制也是JVM的重中之重,调优、监控、面试都逃不开的JVM话题。
|
3月前
|
存储 缓存 算法
(五)JVM成神路之对象内存布局、分配过程、从生至死历程、强弱软虚引用全面剖析
在上篇文章中曾详细谈到了JVM的内存区域,其中也曾提及了:Java程序运行过程中,绝大部分创建的对象都会被分配在堆空间内。而本篇文章则会站在对象实例的角度,阐述一个Java对象从生到死的历程、Java对象在内存中的布局以及对象引用类型。
|
3月前
|
存储 Java 程序员
Java面试题:方法区在JVM中存储什么内容?它与堆内存有何不同?
Java面试题:方法区在JVM中存储什么内容?它与堆内存有何不同?
56 10