Java垃圾回收机制

引言

  我们在之前聊JVM,谈到内存划分时,说到一个栈的栈帧会随着方法的调用结束而被释放,而Java堆和方法区却没有这样的机制,我们知道任何一块空间都不能只进不出,那么堆和方法区中具体是怎么管理内存的呢?这就要说垃圾回收机制了,而说垃圾回收,就首先要判断什么是垃圾。

一、什么是垃圾

  在生活中我们一般把不用的东西、坏掉的东西、或者是已经没有价值的东西称之为垃圾,垃圾没有一个准确的定义,每个人都有不同的判断。但是在计算机世界中,必须要对垃圾有一个准确的判断,才能做出回收;在Java中我们把没有已经没有被引用的对象称之为垃圾,而判断一个对象是不是垃圾有两种方法。

1、引用计数法

  引用计数法是一种简单粗暴的方法,JVM会为每个对象维护一个计数器,对象每次被引用一次,计数器就加1,但引用失效时,计数器就减1;而当引用计数为0时这个对象就会被标记为垃圾;引用计数法实现简单,判定效率也比较高,在大部分情况下都是一个不错的算法。比如Python语言就采用引用计数法进行内存管理(它引入了一个全局解释器锁 GIL)。但是,在主流的JVM中没有选用引用计数法来管理内存,最主要的原因就是引用计数法无法解决对象的循环引用问题,除此之外还有一个原因是因为在多线程时为了保证引用计数的正确性,还需要对计数器加锁,效率也会下降。

public class Test {
    public Object instance = null;
    
    public static void testGC() {
        Test test1 = new Test();
        Test test2 = new Test();
        test1.instance = test2;
        test2.instance = test1;
        test1 = null;
        test2 = null;
        // 强制jvm进行垃圾回收
        System.gc();
    }
}

在这里插入图片描述
这种情况下,他们的引用计数器都不为0,但是实际他们已经找不到了,因为没有指针向他们,这时就会发生内存泄漏。

2、可达性分析算法

  在Java中一般都会采用可达性分析算法去判断这个对象需不需要回收,核心思想是:通过一系列“GC Roots”对象为起始点,然后向下搜索,搜索的路径称为“引用链”,如果一个对象没有任何一个与“GC Roots”相连的引用链,则称这个对象为不可达对象。
在这里插入图片描述

  我们看到上图中虽然红色的对象还储存了下一个结点的引用,但是因为引用是单向的,指向它的引用断了,它就会被标记为不可达。根据上面图所展示的内容其实还有一个疑问,"GC Roots"对象并没有明确指出,如果以第二次那个红色结点为"GC Roots"对象的话,其他的对象就都是不可达的了,所以随之就要说,什么样的对象才可以作为"GC Roots"对象。

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象
  2. 方法区中类静态属性引用的对象
  3. 方法区中常量引用的对象
  4. 本地方法栈中JNI(Native方法)引用的对象

  说完“GC Roots”对象后,我们还要再说一下引用,在Java中,引用被分为了四个类型:强引用、软引用、弱引用、虚引用,我们平常使用的就是强引用,在可达性分析中,对于四种引用的判断各不相同。

  1. 强引用:强引用指的是在程序代码之中普遍存在的,类似于"Object obj = new Object()"这类的引用,只要强引用还存在,垃圾回收器永远不会回收掉被引用的对象实例。
  2. 软引用:软引用是用来描述一些还有用但是不是必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出之前,会把这些对象列入回收范围之中进行第二次回收。如果这次回收还是没有足够的内存,就会释放被软引用引用的对象。
  3. 弱引用:弱引用也是用来描述非必需对象的。但是它的强度要弱于软引用。被弱引用关联的对象只能生存到下一次垃圾回收发生之前。当垃圾回收器开始进行工作时,无论当前内容是否够用,都会回收掉只被弱引用关联的对象。
  4. 虚引用:虚引用也被称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。

  被标记为不可达对象就一定会被回收吗?答案是不一定,一般一个对象至少需要经过两次可达性分析才会被回收,一个对象第一次被标记后JVM会进行一次筛选,判定他它是否需要执行finalize()方法,如果这个对象没有覆盖finalize()方法或者finalize()方法已经被执行过一次了,那么这个对象就会被判断为不需要执行,直接进行回收;如果判定需要执行,就会把这个对象加入一个F-Queue的队列中,并在稍后由一个虚拟机自动建立的、低优先级的Finalizer线程去执行它,就是由JVM去调用这个对象的finalize()方法,之后GC将会对F-Queue进行第二次小规模的标记,如果对象在finalize()中成功拯救自己(只需要重新与引用链上的任何一个对象建立起关联关系即可),那在第二次标记时它将会被移除出"即将回收"的集合;如果对象这时候还是没有逃脱,那基本上它就是真的被回收了。

二、垃圾回收算法

  前面简单的介绍了一个对象被判定为垃圾的规则,那么具体JVM又是怎么回收的呢?GC里面有很多算法,这里主要介绍四种:标记清除法、复制法、标记整理法、分代收集法,这四种算法并不是互相独立的,他们只是一种思想,在实际中可能是多种算法组合一起的。

1、标记清除法

  标记清除法非常简单,首先是利用可达性分析来标记出谁是垃圾,然后释放被标记的对象;这是一个简单且暴力的方法,但是却有一个问题出现,释放内存时可能会产生很多的内存碎片,比如下面这样,而我们在写代码时会涉及到开辟内存空间,一般都是指连续的空间,如果内存碎片化严重,最后可能会抛出堆溢出错误。
在这里插入图片描述

  1. 优点:简单,容易实现
  2. 缺点:回收后碎片化严重

2、复制算法

  复制算法主要是用来解决上述的碎片化问题,首先将内存分成两部分,一部分使用,另外一部分保留,当需要垃圾回收时,先把存活的对象全部移动到另外一部分内存中,然后把这部分内存全部释放掉。
在这里插入图片描述

  1. 优点:解决了内存碎片化的问题,并且当存活对象比较少时,效率比较高。
  2. 缺点:当存活对象较多时,复制的效率就会变低;而且最主要的缺点是内存浪费严重,利用率不高,因为每次只能使用一半的内存。

3、标记整理法

  这个方法既解决了内存碎片化,也提高了利用率,但是因为大量元素的搬移,所以导致效率低下。这三种方法各有优缺点,没有孰优孰劣,只是适用于不同场景。
在这里插入图片描述

4、分代回收

  分代回收其实就是对上述三种算法的应用,把回收的整个过程分不同场景,不同场景下采用不同的回收方式。其核心思想是根据对象存活的不同生命周期将内存划分为不同的域,一般情况下将 GC 堆划分为老年代(Tenured/Old Generation)和新生代(Young Generation)。老年代的特点是每次垃圾回收时只有少量对象需要被回收,新生代的特点是每次垃圾回收时都有大量垃圾需要被回收,因此可以根据不同区域选择不同的算法。

新生代

在这里插入图片描述
  目前大部分JVM对新生代都使用的是复制算法,因为新生代需要回收的对象占大多数,需要拷贝的对象很少,所以为了提高利用率,并不会像上述将的那样把内存(新生代)等分成两个部分,而是分成一块较大的Eden(伊甸园)空间和两块较小的Survivor(幸存者)空间。具体过程如下:

  1. 当Eden区满的时候,会触发第一次Minor GC,把还活着的对象拷贝到Survivor From区,然后清空Eden区;当Eden区再次触发Minor GC的时候,会扫描Eden区和From区域,对两个区域进行垃圾回收,经过这次回收后还存活的对象,则直接复制到To区域,并将Eden和From区域清空。
  2. 当后续Eden区和To区域再次满的时候,会再次触发Minor GC,把活着的对象拷贝到From区,然后清空Eden和From区。
  3. 部分对象在From区和To区交换15次(由JVM参数决定),就会进入老年代,但是也有特殊情况,比如对象太大,From区和To区不能存放时,这个对象就会直接被放到老年代中。

老年代

  老年代的对象比较稳定,所以 MajorGC 不会频繁执行。在进行 MajorGC 前一般都先进行了一次 MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次 MajorGC 进行垃圾回收腾出空间,如果还没有足够的空间就会抛出OOM异常。老年代一般会采用标记清除或者是标记整理算法,这个会根据JVM来实现。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值