13-大厂面试题:为什么要垃圾回收以及如何判断对象可以回收

简介: 接下来我们正式进入第二个系列,关于垃圾回收以及优化。

对于从事C、 C++程序开发的开发人员来说, 在内存管理领域, 他们既是拥有最高权力的“皇帝”,又是从事最基础工作的劳动人民——既拥有每一个对象的“所有权”, 又担负着每一个对象生命从开始到终结的维护责任。

对于Java程序员来说, 在虚拟机自动内存管理机制的帮助下, 不再需要为每一个new操作去写配对的delete/free代码, 不容易出现内存泄漏和内存溢出问题, 看起来由虚拟机管理内存一切都很美好。 不过, 也正是因为Java程序员把控制内存的权力交给了Java虚拟机, 一旦出现内存泄漏和溢出方面的问题, 如果不了解虚拟机是怎样使用内存的, 那排查错误、 修正问题将会成为一项异常艰难的工作

1.为什么要垃圾回收

通过之前内存结构的知识讲解,我们知道,存储在JVM中的Java对象可以被划分为两类:

  ➷ 一类是生命周期较短的瞬时对象,这类对象的创建和消亡都非常迅速,生命周期短的,及时回收即可。

  ➷ 另外一类对象的生命周期却非常长,在某些极端的情况下还能够与JVM的生命周期保持一致。

存活较短的对象存放在新生代,存活较长的对象存放在老年代。

我们来一起看下如下的代码,对他们在内存中的分配做一个剖析:

public class Test {
   
   
    private static User user = new User();
    public static void main(String[] args) throws InterruptedException {
   
   
        user.login();
        for (int i = 0; i < 10; i++) {
   
   
            doSomething();
            Thread.sleep(2000);
        }
    }
    public static void doSomething(){
   
   
       Student stu = new Student();
       stu.study();
    }
}
class User{
   
   
    public void login(){
   
   
        System.out.println("登录");
    };
}
class Student{
   
   
    public void study(){
   
   
        System.out.println("I'm studying");
    };
}
AI 代码解读

Test类中静态成员变量 user 是长期存活的,并且分配在新生代中。

main方法中通过for循环调用了10次 doSomething() 方法,方法中会创建Student()对象。

我们先将时间定格在执行完第一次后,内存中的分配情况是:

以上仅仅是执行第一次doSomething() 方法后的情况,如果执行10次后的情况会发生什么样的变化呢?

首先大家要明确,我们的doSomething() 方法执行完后对应的栈帧肯定会弹栈,那么对应栈帧的局部变量也相应被释放回收,我们堆内存中的实例对象就会变成无引用的垃圾对象了:

当最后一次 doSomething() 方法执行完后 对应栈帧弹栈,那么堆内存中新生代里面的Student实例对象就存在了有10个对象没有地址引用,后续如果再继续产生一些垃圾对象,当新生代中的内容空间已无法分配空间的时候,就会进行“ Minor GC ”,将对应新生代的垃圾对象进行回收:

回收后的内存就仅剩User这个对象了:

当然如果我们的程序在经历了15次“ Minor GC”后还没有被回收的对象就会被放入我们的老年代进行管理;比如我们的User实例对象,因为一直被Test类静态变量引用,所以它不会被回收。

当然如果老年代里面的空间也存满了后,也会触发垃圾回收,把老年代中没用的垃圾对象进行清理。

2.大厂面试题-如何判断对象可以回收

接下来我们继续分析,当触发垃圾回收的时候,我们的JVM到底按照一个什么样的规则来回收垃圾对象。到底哪些对象可以被回收,哪些对象不能被回收。我们先来说一个可达性分析算法:

2.1可达性分析算法

当前主流的商用程序语言(Java、 C#, 上溯至古老的Lisp) 的内存管理子系统, 都是通过可达性分析(Reachability Analysis) 算法来判定对象是否存活的。 这个算法的基本思路就是通过一系列称为“GC Roots”的根对象作为起始节点集, 从这些节点开始, 根据引用关系向下搜索, 搜索过程所走过的路径称为“引用链”(Reference Chain) , 如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是从GC Roots到这个对象不可达时, 则证明此对象是不可能再被使用的。

总结下就是: 每一个对象,都分析下有谁在引用他,然后一层一层往上去判断,看是否有一个GC Roots

那么有哪些是被作为是GC Root对象的?

在Java技术体系里面, 固定可作为GC Roots的对象包括以下几种:

  • 在虚拟机栈(栈帧中的本地变量表) 中引用的对象, 譬如各个线程被调用的方法堆栈中使用到的参数、 局部变量、 临时变量等。
  • 在方法区中类静态属性引用的对象, 譬如Java类的引用类型静态变量。
  • 在方法区中常量引用的对象, 譬如字符串常量池(String Table) 里的引用。
  • 在本地方法栈中JNI(即通常所说的Native方法) 引用的对象。
  • Java虚拟机内部的引用, 如基本数据类型对应的Class对象, 一些常驻的异常对象(比如NullPointExcepiton、 OutOfMemoryError) 等, 还有系统类加载器。
  • 所有被同步锁(synchronized关键字) 持有的对象。
  • 反映Java虚拟机内部情况的JMXBean、 JVMTI中注册的回调、 本地代码缓存等

在我们刚才的代码中:

public class Test {
   
   
    private static User user = new User();
    public static void main(String[] args) throws InterruptedException {
   
   
        user.login();
        for (int i = 0; i < 10; i++) {
   
   
            doSomething();
            Thread.sleep(2000);
        }
    }
    public static void doSomething(){
   
   
        //根对象
       Student stu = new Student();
       stu.study();
    }
}
AI 代码解读

局部变量stu就是一个GC Roots,这种也是最常见的一种情况。假如代码正在调用执行doSomething()方法,这时局部变量stu持有实例对象,新生代空间存满,发生垃圾回收,就会去分析这个stu对象的可达性,这时发现stu对象被局部变量GC Roots持有无法回收。

类静态变量也是一种常见的GC Roots,上述代码中的user对象就是被user静态变量持有,那么当发生垃圾回收的时候也不会被回收。

小结

通过本节的内容学习,相信大家已经搞懂了为什么要垃圾回收以及通过可达性算法分析哪些对象是可以被回收的,哪些对象是存活的,常见的两种GC Roots希望大家记住:1是我们的局部变量引用,2是我们的类静态变量引用。通过GC Roots引用的对象是无法被回收的。

那么除了被GCRoots引用的对象无法被回收以外,java还有哪些对象是可以回收的或不能回收的呢?下一节我们将重点介绍Java的引用类型:强引用、软引用、弱引用、虚引用这四者的概念以及对象的finalization机制。

目录
相关文章
|
5月前
|
Python内存管理:掌握对象的生命周期与垃圾回收机制####
本文深入探讨了Python中的内存管理机制,特别是对象的生命周期和垃圾回收过程。通过理解引用计数、标记-清除及分代收集等核心概念,帮助开发者优化程序性能,避免内存泄漏。 ####
107 3
JVM常见面试题(四):垃圾回收
堆区域划分,对象什么时候可以被垃圾器回收,如何定位垃圾——引用计数法、可达性分析算法,JVM垃圾回收算法——标记清除算法、标记整理算法、复制算法、分代回收算法;JVM垃圾回收器——串行、并行、CMS垃圾回收器、G1垃圾回收器;强引用、软引用、弱引用、虚引用
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
276 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程 ?
尼恩提示: G1垃圾回收 原理非常重要, 是面试的重点, 大家一定要好好掌握
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程  ?
【Vue面试题九】、Vue中给对象添加新属性界面不刷新?
这篇文章讨论了Vue中给对象动态添加新属性时界面不刷新的问题,并提供了三种解决方案:使用`Vue.set()`方法来确保新属性是响应式的并触发视图更新,使用`Object.assign()`创建新对象以合并新属性,以及作为最后手段的`$forceUpdate()`进行强制刷新。文章还简要分析了Vue 2和Vue 3在数据响应式实现上的差异。
【Vue面试题八】、为什么data属性是一个函数而不是一个对象?
这篇文章解释了为什么在Vue中组件的`data`属性必须是一个函数而不是一个对象。原因在于组件可能会有多个实例,如果`data`是一个对象,那么这些实例将会共享同一个`data`对象,导致数据污染。而当`data`是一个函数时,每次创建组件实例都会返回一个新的`data`对象,从而确保了数据的隔离。文章通过示例和源码分析,展示了Vue初始化`data`的过程和组件选项合并的原理,最终得出结论:根实例的`data`可以是对象或函数,而组件实例的`data`必须为函数。
【Vue面试题八】、为什么data属性是一个函数而不是一个对象?
Java面试题:描述Java垃圾回收的基本原理,以及如何通过代码优化来协助垃圾回收器的工作
Java面试题:描述Java垃圾回收的基本原理,以及如何通过代码优化来协助垃圾回收器的工作
124 8
Java面试题:如何在Java中触发一次Full GC?请详细解释垃圾回收机制和知识
Java面试题:如何在Java中触发一次Full GC?请详细解释垃圾回收机制和知识
565 4
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!

热门文章

最新文章