垃圾回收机制

垃圾回收机制是用来干什么的?
当启动一个JVM进程时,会自动启动一个垃圾回收线程,这个垃圾回收线程会在后台不断地检查堆中各个实例对象,如果某个实例对象没有任何局部变量、静态变量指向它,这个实例对象就是垃圾对象,垃圾回收线程就会这个垃圾对象回收掉,从内存中清理掉。

为什么要有垃圾回收?
由于内存资源是有限的,给JVM进程分配的内存资源也是有限的,如果没有垃圾回收,一些已经失去引用的垃圾对象会在内存中越积越多,导致内存泄漏,无法分内存给新创建的实例对象。垃圾回收可以帮助我们清理调不可达对象,即没有被局部变量、静态变量引用的对象。

Java 垃圾回收中是如何判断一个对象死亡的?

  1. 引用计数法:当对象被引用时给计数器+1,当对象引用失效时计数器-1。当计数器为0时,说明对象不在被引用,可以回收。
  2. 可达性分析法:JVM使用了一种可达性分析算法来判断哪些对象是可以回收的,那么些对象是不可回收的,不可回收的对象都是死亡的。该算法会检查每个对象,分析一下有谁在引用他,然后一层一层往上去判断,看是否有一个GC Roots。在JVM规范中,局部变量、静态变量都可以作为GC Roots。一句话总结就是,只要对象被局部变量、静态变量引用着,就不会回收它们。

刚才说到了引用计数法,引用计数法存在什么问题?
如图所示,引用计数法只关注对象是否有引用,不关注这个引用来自哪里,如果两个对象相互引用,形成循环依赖,即在堆内存中,两个对象的实例变量相互指向对方,此时即使这两个对象没有其它局部变量、静态变量指向,引用计数器也不会为零,会一致存在在内存中。
在这里插入图片描述
刚才说到了可达性分析,知道哪些可以作为 GC Roots 吗?

  1. 当前线程调用方法时,会将方法对应的栈帧压入当前线程的虚拟机栈,栈帧里面会存放局部变量、方法调用等信息,如果一个局部变量引用了堆中的对象,那么这个局部变量就是GC Roots
  2. 类静态变量
  3. Java本地方法中引用了某个对象,这个本地方法也可以作为GC Roots

垃圾回收算法介绍一下?

  1. 标记清除算法:标记整理算法的思路很简单,就是利用可达性分析法,判断每个对象是否可达,即是否存在GC Roots,如果不可达,就标记为垃圾对象,标记完所有的垃圾对象后清理即可。这种算法的缺点是会产生大量的内存碎片,就是内存看起来是足够的,但是想要用一块连续的内存空间分配对象还是不行。
  2. 标记复制算法:标记复制算法是堆标记清除算法的改进,最初的时候,标记复制算法把堆中的新生代区域划分为两块内存区域,然后不断选择其中一块给对象分配内存空间,当这个块内存空间快满了的时候,利用可达性分析法将垃圾对象标记出来,然后将存活对象复制到另一块内存区域中,最后一次性清除当前这块区域的垃圾对象。这个算法的缺点就是内存利用率太低,只有一半的内存空间被使用,后来人们做了改进,将新生代划分为一块Eden区和两块Surrvior区,Eden区专门用来存放新创建的对象,当Eden区满的时候,会触发垃圾回收,将存活对象移入一块Surrvivor,然后一次性清除Eden区里面的对象。这样划分内存空间的好处是可以提高内存利用率,如果将Eden区和两块Surviror的比例划分为8:1:1,则内存利用率为90%。
  3. 标记整理算法:该算法进一步提高的内存利用率,当触发垃圾回收时,经过标记,会将对象分为存活对象和垃圾对象,然后,该算法会将存活对象移入内存区域的一边,把垃圾对象移入当前空间的另一边,然后一次性清除垃圾对象,这样即解决了垃圾碎片的问题,也没有降低内存利用率。

什么情况下JVM内存中的一个对象会被垃圾回收?

新生代快满的时候,就会触发新生代的垃圾回收,此时JVM怎么判断哪些对象是能回收的,哪些对象是不能回收的呢?
JVM使用了一种可达性分析算法来判断哪些对象是可以回收的,那么些对象是不可回收的。该算法会检查每个对象,分析以下有谁在引用他,然后一层一层往上去判断,看是否有一个GC Roots。
总结: 有GC Roots引用的对象不能回收,没有GC Roots引用的对象可以回收。如果有GC Roots引用,但是软引用或者弱引用,也有可能被回收掉。

垃圾回收会发生在哪几个区域?
垃圾回收主要发生在堆上,尤其是在新生代中。一次垃圾回收通常可以回收70%到90%的内存空间,这是因为我们在代码中创建的大多数对象不是长期存在的对象。也会对方法区进行垃圾回收,对方法区主要回收的是常量和不在使用的类,回收的空间一般比较小。

循环依赖

循环依赖就是两个或者多个对象互相引用,形成一个闭环。

比如,让我们创建两个类,A和B:

class A {
    B instanceB;
}

class B {
    A instanceA;
}

然后我们创建A和B的实例,并使它们互相引用:

void createCircularDependency() {
    A objectA = new A();
    B objectB = new B();

    objectA.instanceB = objectB;
    objectB.instanceA = objectA;
}

在这个例子中,objectA引用了objectBobjectB又引用了objectA,形成了一个循环依赖。在引用计数法的垃圾回收策略中,只要一个对象被引用,其计数就不为0,就不会被回收。所以即使objectAobjectB在方法createCircularDependency执行完后不再被其他代码所引用,它们的引用计数依然不为0(因为它们互相引用),所以它们不会被引用计数法标记为可回收,从而导致内存泄漏。

这就是引用计数法的一个主要缺点,它无法处理循环依赖的情况。而其他一些垃圾回收算法,如标记-清除(Mark-Sweep)和复制(Copying)等,可以有效地处理循环依赖的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值