当前位置:   article > 正文

一文彻底搞懂Java对象什么时候被垃圾器回收_在java中对象什么时候可以被垃圾回收?

在java中对象什么时候可以被垃圾回收?

1. 简介

Java 是一门不需要自己手动控制内存释放的语言,有自动的垃圾回收机制,也就是我们熟悉的GC(Garbage Collection)。有了垃圾回收机制后,程序员只需要关心内存的申请即可,内存的释放由系统自动识别完成。

在进行垃圾回收时,不同的对象引用类型,GC会采用不同的回收时机,这时自动的垃圾回收的算法就会变得非常重要了,如果因为算法的不合理,导致内存资源一直没有释放,同样也可能会导致内存溢出的。

在这里插入图片描述

  • 如果一个或多个对象没有任何的引用指向它了,那么这个对象现在就是垃圾,如果定位了垃圾,则有可能会被垃圾回收器回收。
  • 如果要定位什么是垃圾,有两种方式来确定,第一个是引用计数法,第二个是可达性分析算法。

2. 引用计数法

主要思想是通过记录对象被引用的次数来判断对象是否可回收。当一个对象被创建时,引用计数加一;当一个引用指向该对象时,引用计数也加一;当引用失效或对象被销毁时,引用计数减一。当对象的引用计数变为零时,即表示该对象不再被任何引用指向,可以被垃圾收集器回收。

String demo = new String("123");
  • 1

在这里插入图片描述

String demo = null;
  • 1

在这里插入图片描述
JVM已经放弃了引用计数算法,这是因为当对象间出现了循环引用的话,则引用计数法就会失效。
在这里插入图片描述
虽然a和b都为null,但是由于a和b存在循环引用,这样a和b永远都不会被回收。

2.1 优点

  • 实时性:引用计数算法可以及时回收不再被引用的对象,无需等待特定的垃圾收集周期。
  • 简单高效:相对于其他垃圾收集算法,引用计数算法实现相对简单,在垃圾回收过程中,应用无需挂起。如果申请内存时,内存不足,则立刻报OOM错误。
  • 区域性:更新对象的计数器时,只是影响到该对象,不会扫描全部对象。

2.2 缺点

  • 循环引用问题:引用计数算法无法解决循环引用问题。即使对象之间相互引用,导致它们的引用计数都不为零,但实际上它们已经不再被外部引用,应该被回收。这种情况下,引用计数算法会导致内存泄漏。
  • 维护开销大:引用计数算法需要维护每个对象的引用计数,当对象被频繁引用和释放时,会增加计数器的操作开销。
  • 无法处理对象的相互引用:由于引用计数算法无法处理循环引用问题,因此无法处理对象之间的相互引用情况,容易导致内存泄漏。
  • 内存开销大:引用计数算法需要为每个对象维护一个额外的引用计数字段,增加了对象的内存开销。

3. 可达性分析算法

该算法会存在一个根对象【GC Roots】,递归地遍历所有可通过引用链访问到的对象,搜索过的路径被称为(Reference Chain),判断某对象是否与根对象有直接或间接的引用,如果没有被引用,则可以当做垃圾回收。现在JVM采用的都是通过可达性分析算法来确定哪些内容是垃圾。

在这里插入图片描述
X,Y这两个节点是可回收的,但是并不会马上的被回收。对象中存在一个方法finalize。当对象被标记为可回收后,当发生GC时,首先会判断这个对象是否执行了finalize方法,如果这个方法还没有被执行的话,那么就会先来执行这个方法,接着在这个方法执行中,可以设置当前这个对象与GC ROOTS产生关联,那么这个方法执行完成之后,GC会再次判断对象是否可达,如果仍然不可达,则会进行回收,如果可达了,则不会进行回收。
finalize方法对于每一个对象来说,只会执行一次。如果第一次执行这个方法的时候,设置了当前对象与RC ROOTS关联,那么这一次不会进行回收。 那么等到这个对象第二次被标记为可回收时,那么该对象的finalize方法就不会再次执行了。

根对象是那些肯定不能当做垃圾回收的对象,就可以当做根对象。在Java中,GC Roots包括四种:

  • 虚拟机栈中的引用对象
  • 方法区静态属性引用的对象
  • 方法区常量引用的对象
  • 本地方法栈JNI引用的对象

在这里插入图片描述

3.1 虚拟机栈(栈帧中的本地变量表)中引用的对象

/**
* demo是栈帧中的本地变量,当demo = null时,由于此时demo充当了GC Root的作用,
* demo与原来指向的实例new Demo()断开了连接,对象被回收。
*/
public class Demo {
public static void main(String[] args) {
Demo demo = new Demo();
demo = null;
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

3.2 方法区中静态属性引用的对象

/**
* 当栈帧中的本地变量b = null时,由于b原来指向的对象与GC Root (变量b) 断开了连接,
* 所以b原来指向的对象会被回收,而由于我们给a赋值了变量的引用,a在此时是类静态属性引用,
* 充当了 GC Root 的作用,它指向的对象依然存活!
*/
public class Demo {
public static Demo a;
public static void main(String[] args) {
Demo b = new Demo();
b.a = new Demo();
b = null;
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

3.3 方法区中常量引用的对象

/**
* 常量 a 指向的对象并不会因为 demo 指向的对象被回收而回收
*/
public class Demo {
public static final Demo a = new Demo();
public static void main(String[] args) {
Demo demo = new Demo();
demo = null;
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

3.4 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象

任何 Native 接口都会使用某种本地方法栈,实现的本地方法是使用 C 连接模型的话,那么它的本地方法栈就是 C 栈。
当线程调用 Java 方法时,虚拟机会创建一个新的栈帧并压入 Java 栈,然而当它调用的是本地方法时,虚拟机会保持 Java 栈不变,虚拟机只是简单地动态连接并直接调用指定的本地方法。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/397756
推荐阅读
相关标签
  

闽ICP备14008679号