Java垃圾回收的时间点

Java与C++很大的一个不同点就是Java有自己的垃圾回收系统,可以让程序员在清理垃圾方面不必下过多的功夫。

垃圾回收系统主要分为三部分

1.判别那些对象是垃圾

2.垃圾怎么进行回收

3.什么时候回收垃圾

关于1和2大多数人都知道,判别垃圾的可达性算法和垃圾回收的分代收集法这些大家都有自己的理解了,但是虚拟机究竟是什么时候进行垃圾回收呢?笔者通过这几天的读书,对这方面的知识做了自己的总结。

什么时候GC

首先来说什么时候发生GC(垃圾回收),一下想到的肯定是:内存不够用了就清理一下呗。的确大体上是这样的,我们从虚拟机的角度来理解这个问题,当进行垃圾回收的时候肯定会先进行可达性算法的遍历,这时肯定是要所有的线程的停下来等待可达性算法线程标记对象的,这个过程被称为“Stop The World”,名字很帅啊。

究竟是什么类型的数据

继续往下说之前,先说一下Java中数据的两大分类吧,分别是基本类型和引用类型,这两个类型的数据虽然都是放在堆上的,但是在调用时的行为截然不同啊,于是Java虚拟机利用一个称为OopMap的数据类型去记录哪些变量是基本类型,哪些变量是引用类型,而且也会在特定的位置记录这些信息。

由于垃圾回收时对两种数据类型的操作是不同的,所以Java垃圾回收机肯定要知道现在回收的这个对象究竟是引用还是基本类型,上文说Java虚拟机会把区分对象类型的信息记录在特定的位置,所以在垃圾回收时必须要在这个特定位置进行(这样才能得到对象的类型信息)。这个特定位置被称为“安全点”。

安全点在哪里,有多少

说到这里脑中会产生一个想法:

Java垃圾回收进行的还是比较频繁的(尤其是Minor GC),所以“安全点”应该基本是处处存在的啊,这样才能在发生GC时提供“服务”。

错!如果处处都有安全点去记录信息,那么内存的开销会变得非常大,但是如果安全点过于少的话也不行啊,GC都发生很长时间了,但是就因为无法得到数据类型信息一直无法继续前行。所以“安全点”的位置和出现次数是非常讲究的,“安全点”的选定是根据“是否具有让程序长时间执行的特征”来决定的。Java虚拟机是通过执行一条条指令来运作的(安全点也是记录到某一条的指令上),指令执行都是非常快的(不然我们也不会有那么流畅的体验啊!),所以长时间让程序执行的指令一般是那些方法调用,循环跳转,异常跳转等,这些操作会花一些相对较长的时间而且基本不会产生新的对象,也就是这段时间整个线程的对象总数基本是确定的,所以在这个时间点进行GC是比较理想的。

 

GC信号与安全点

继续我们的GC流程,内存不够了,Java虚拟机开始发生GC。这时Java虚拟机会发出一个GC,告诉每个线程“发生GC了,赶紧在最近的安全点停下来去回收垃圾”这个信息。然后各个线程都注意到了这个信息,于是在各自的安全点回收当前线程的垃圾对象。问题又来啦!

A:线程是怎么发现GC信号的呢?

B:当然是在执行过程中去实时检测GC信号的啊

A:那要是总检测GC信号,也太浪费资源了吧!

B:当然是在特定的位置检测了

没错,检测的位置就是就是“安全点”,每次Java虚拟机执行到标记为安全点的指令时都会进行一次GC信号检测,如果没有就继续跑指令了,如果有就直接进行GC,反正现在的位置就是安全点,执行GC没毛病(真是佩服那些设计者们,想出这么巧妙的方法)。

在GC信号发出时,各个线程运行到“安全点”就会发现信号,然后停止线程进行GC操作,当所有的线程都回收完毕时,这次GC就结束了。

线程正在执行各自的任务,这时发生了GC,Java虚拟机发出GC信号。

各个线程到了“安全点”,检测到了GC信号,开始进行GC。

每个线程都完成垃圾回收,整个GC结束,各个线程恢复执行。

阻塞呢,无法检测“安全点”

还有一个问题,当程序在等待io操作,或者在sleep的状态怎么办呢?这时程序无法继续向下执行,不能实时检测GC信号,这次GC是不是就错过了?

当然不会啊,有一个特殊的“安全点”可以解决这个问题。

安全区域
如果说安全点是一个“点”,那么“安全区域”就是把这个点进行一个拉长操作,变成一条线,在这条线包含那一片区域就是“安全区域”。

等待io等线程阻塞非常适合放到“安全区域”中,因为在这段时间中,线程就是处于一个等待的状态,基本不会产生新的对象,适合去进行GC。当线程进入“安全区域”时,会把给自己贴上一个安全标签,告诉虚拟机自己现在在“安全区域”,随时可以进行GC。这时发生GC,虚拟机看到这个线程有安全标签,根本就不会管他,直接就是一顿GC全家桶伺候。这时的GC可以说这个线程是不知情的(因为线程在阻塞啊,处于专注的等待状态或者睡得正香),所以当这个线程重新跑以来以后,在它走出安全区时会检测一下虚拟机是不是在GC的过程中,如果是就等待GC完成,如果不是就直接继续运行(管你GC完成了还是压根没GC,反正我已经给过你机会了)。

“安全区”和“安全区域”基本上可以解决所以的问题,所以在GC什么时候进行这一块也就结束了。

线程在安全区域外正常执行

线程执行到安全区域,标记当前线程为安全状态,然后进入了io阻塞

这时发生GC,Java虚拟机查询线程的安全标志,发现此线程处于安全区域,于是直接开始GC。

GC完成,线程从阻塞态变为运行态重新开始运行,走出安全区域时,检测是否有GC在执行中,并没有发现,于是走出安全区域继续执行下去。

Stop The World

接下来说一点题外话,还记得刚开始说的Stop The World吗?由于GC的进行所以暂时让线程停下来。判别对象是否是垃圾的对象的算法是可达性算法,具体是怎么个算法就不在这里细说了。现在有个问题就是在STW(Stop The World)这段时间是遍历所有的对象进行标记吗?

当然不是了,如果是这样的话,那么在大型的服务器上一个GC岂不是要让程序停顿很长时间吗?在近些年推出的高级垃圾收集器中(CMS,G1)STW这段时间是用来给GC root引用的对象做标记的,只要每个根引用的对象被做了标记就结束STW,然后与各个线程并行的去遍历整个对象链。这样一来,可以节省很大一部分时间啊。

最后

Java虚拟机在垃圾方面在不同的内存带和不同的收集器上都有或多或少的不同。在Java团队对JDK的一次次更新中,我们这些使用者真的是获得了越来越好的体验了,谢谢他们的付出。如果大家想了解更详细的信息可以阅读《深入理解Java虚拟机》这本书,写的非常棒。本文只是笔者自己的理解和总结,如果有错误的地方还请大家指出。

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Java垃圾回收算法主要基于可达性分析和标记-清除两种算法。下面是对这两种算法的简要说明: 1. 可达性分析 (Reachability Analysis):这是Java垃圾回收的基础算法。它通过判断对象是否可以从根对象(如线程栈、静态变量等)访问到来确定对象的存活状态。如果一个对象不可达,则认为它是垃圾,可以被回收。 2. 标记-清除 (Mark and Sweep):这是最基本的垃圾回收算法之一。在标记阶段,垃圾回收器从根对象开始遍历所有可达对象,并将其标记为“存活”。在清除阶段,垃圾回收器清除所有未被标记的对象,并回收它们所占用的内存空间。 除了标记-清除算法,Java还使用了其他一些高级的垃圾回收算法,包括: 1. 复制算法 (Copying Algorithm):将堆内存分为两个区域,每次只使用其中一个区域。当一个区域满了之后,将存活的对象复制到另一个区域中,并清除当前区域中的所有对象。 2. 标记-整理 (Mark and Compact):类似于标记-清除算法,但在清除阶段之后,它会将存活的对象移动到内存的一端,以便于分配连续的内存空间。 3. 分代算法 (Generational Algorithm):根据对象的存活时间将堆内存划分为不同的代。通常情况下,新创建的对象会被分配到年轻代,而存活时间较长的对象则会被转移到老年代。不同代使用不同的垃圾回收算法进行回收。 这些算法的选择取决于具体的应用场景和性能需求,Java垃圾回收器通常会根据当前堆内存的使用情况和对象的存活特性来选择合适的回收算法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值