11、JVM 调优实战 - 面试题:什么情况下JVM内存中的一个对象会被垃圾回收?

1. 什么时候会触发垃圾回收?

系统运行创建的对象都是优先分配在新生代里的,如下图:

 

如果新生代里的对象越来越多,都快满了,此时就会触发垃圾回收,把新生代没有人引用的对象给回收掉,释放内存空间。

 

2. 被哪些变量引用的对象是不能回收的?

在JVM中使用了一种可达性分析算法来判定哪些对象是可以被回收的,哪些对象是不可以被回收的。

该算法的意思,是对每个对象,都分析一下有谁在引用它,然后一层一层往上去判断,看是否有一个GC Roots。

 

如上述代码,它是在一个方法中创建了一个对象,然后有一个局部变量引用了该对象。

 

假设上图 “ReplicaManager” 对象被局部变量给引用了,此时一旦新生代快满了,发生垃圾回收,会去分析这个 “ReplicaManager” 对象的可达性。

这时,发现它时不能被回收的,因为它被人引用了,而且是被局部变量 “replicaManager” 引用的。

在JVM规范中,局部变量可以看做是 GC Roots的。所以此时这个对象如果被局部比那里引用了,就不能被回收了。

在JVM规范中,静态变量也可以看做是一种GC Roots。如下图:

 

上图所示,垃圾回收的时候一分析,发现 “ReplicaManager” 对象被Kafka类的一个静态变量 “replicaManager” 给引用了。

所以,此时只要一个对象被静态对象的 GC Roots 引用了,就不会去回收它。

一句话总结:只要你的对象被方法的局部变量、类的静态变量给引用了,就不会回收他们。

3. Java中对象不同的引用类型

关于引用和垃圾回收的关系,这里引入一个新的概念,那就是Java里有不同的引用类型。分别是强引用、软引用、弱引用和虚引用

1、 强引用,示例代码如下:

 

一个变量引用一个对象,只要是强引用的类型,那么垃圾回收的时候绝对不会去回收这个对象。

2、 软引用,示例代码如下:

 

上述代码中,“ReplicaManager” 实例对象被一个 “SoftReference ” 软引用类型的对象给包裹起来了,此时这个 “replicaManager” 变量对 “ReplicaManager” 对象的引用就是软引用。

正常情况下垃圾回收是不会回收软引用对象的,但是如果进行垃圾回收后,内存空间还是不够存放新的对象,即将溢出了。

此时就会把这些软引用对象给回收掉,哪怕它被变量引用了,但因为是软引用,所以还是要回收的。

3、 弱引用,示例代码如下:

 

这里的弱引用和没引用类似,如果发生垃圾回收,就会把这个对象回收掉。

4、 虚引用,一般很少使用,不谈。

总结,比较常用的,主要是强引用和软引用,强引用就是代表绝对不能回收的对象,软引用就是说有的对象可有可无,如果内存是在不够了,可以回收它。

4. GC Roots 和引用类型的概念总结:

有GCRoots引用的对象不能回收,没有GC Roots引用的对象可以回收,如果有GC Roots引用,但是如果是软引用或者弱引用的,也有可能被回收掉。

5. finalize() 方法的作用

在回收的环节,如果没有GC Roots引用的对象,也不是一定立马被回收掉。如下代码:

 

假设有一个ReplicaManager 对象要被垃圾回收了,如果这个对象重写了 Object类的 finialize() 方法。

此时就会先尝试调用一下它的 finalize() 方法,看是否把自己这个实例对象给了某个GC Roots变量,比如说代码中就给了 ReplicaMnager 类的静态变量。

如果重新让某个GC Roots变量引用了自己,那么就不用被垃圾回收了。