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");
    };
}

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();
    }
}

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

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

小结

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

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

目录
相关文章
|
3月前
|
存储 缓存 监控
Java面试题:在Java中,对象何时可以被垃圾回收?编程中,如何更好地做好垃圾回收处理?
Java面试题:在Java中,对象何时可以被垃圾回收?编程中,如何更好地做好垃圾回收处理?
54 0
|
2月前
|
JavaScript
【Vue面试题九】、Vue中给对象添加新属性界面不刷新?
这篇文章讨论了Vue中给对象动态添加新属性时界面不刷新的问题,并提供了三种解决方案:使用`Vue.set()`方法来确保新属性是响应式的并触发视图更新,使用`Object.assign()`创建新对象以合并新属性,以及作为最后手段的`$forceUpdate()`进行强制刷新。文章还简要分析了Vue 2和Vue 3在数据响应式实现上的差异。
|
2月前
|
JavaScript
【Vue面试题八】、为什么data属性是一个函数而不是一个对象?
这篇文章解释了为什么在Vue中组件的`data`属性必须是一个函数而不是一个对象。原因在于组件可能会有多个实例,如果`data`是一个对象,那么这些实例将会共享同一个`data`对象,导致数据污染。而当`data`是一个函数时,每次创建组件实例都会返回一个新的`data`对象,从而确保了数据的隔离。文章通过示例和源码分析,展示了Vue初始化`data`的过程和组件选项合并的原理,最终得出结论:根实例的`data`可以是对象或函数,而组件实例的`data`必须为函数。
【Vue面试题八】、为什么data属性是一个函数而不是一个对象?
|
3月前
|
缓存 监控 算法
Java面试题:描述Java垃圾回收的基本原理,以及如何通过代码优化来协助垃圾回收器的工作
Java面试题:描述Java垃圾回收的基本原理,以及如何通过代码优化来协助垃圾回收器的工作
73 8
|
3月前
|
监控 算法 Java
Java面试题:如何在Java中触发一次Full GC?请详细解释垃圾回收机制和知识
Java面试题:如何在Java中触发一次Full GC?请详细解释垃圾回收机制和知识
258 4
|
5月前
|
算法 Java
JVM GC和常见垃圾回收算法
JVM GC和常见垃圾回收算法
83 0
|
12天前
|
监控 算法 Java
深入理解Java中的垃圾回收机制在Java编程中,垃圾回收(Garbage Collection, GC)是一个核心概念,它自动管理内存,帮助开发者避免内存泄漏和溢出问题。本文将探讨Java中的垃圾回收机制,包括其基本原理、不同类型的垃圾收集器以及如何调优垃圾回收性能。通过深入浅出的方式,让读者对Java的垃圾回收有一个全面的认识。
本文详细介绍了Java中的垃圾回收机制,从基本原理到不同类型垃圾收集器的工作原理,再到实际调优策略。通过通俗易懂的语言和条理清晰的解释,帮助读者更好地理解和应用Java的垃圾回收技术,从而编写出更高效、稳定的Java应用程序。
|
16天前
|
监控 算法 Java
深入理解Java中的垃圾回收机制(GC)
本文将探讨Java的自动内存管理核心——垃圾回收机制。通过详细解析标记-清除算法、复制算法和标记-整理算法等常用垃圾回收算法,以及CMS、G1等常见垃圾回收器,帮助读者更好地理解Java应用的性能优化和内存管理。同时,探讨分代收集、分区收集等策略在实际项目中的应用。结语部分总结了垃圾回收机制在Java开发中的重要性,并展望了未来可能的发展。
19 0
|
2月前
|
缓存 监控 Java
"Java垃圾回收太耗时?阿里HBase GC优化秘籍大公开,让你的应用性能飙升90%!"
【8月更文挑战第17天】阿里巴巴在HBase实践中成功将Java垃圾回收(GC)时间降低90%。通过选用G1垃圾回收器、精细调整JVM参数(如设置堆大小、目标停顿时间等)、优化代码减少内存分配(如使用对象池和缓存),并利用监控工具分析GC行为,有效缓解了高并发大数据场景下的性能瓶颈,极大提升了系统运行效率。
53 4
|
2月前
|
算法 Java 应用服务中间件
探索JVM垃圾回收算法:选择适合你应用的最佳GC策略
探索JVM垃圾回收算法:选择适合你应用的最佳GC策略
下一篇
无影云桌面