JVM学习——第四篇:垃圾与收集算法

1、如何判断垃圾

        对于jvm来说只要对象不再被使用了,那它就是一个垃圾,就可以回收所占用的空间了。那如何判断对象是否不再被使用呢?

        引用计数算法:在对象中添加一个引用计数器,对象被引用计数器+1,当引用失效则-1,任何时刻计数器为0,说明对象没有被引用可以回收。引用计数法思路很简单,但是缺陷也很明显,那就是循环引用:比如对象A里面引用了对象B,对象B里面又引用了对象A就无法判断是否可以回收。

        可达性分析算法:GC Roots根节点出发向下搜索,搜索过程走过的路径称为引用链,如果当前对象到GC Roots没有任何引用链,也就是说从GC Roots到这个对象不可达,那就证明这个对象是垃圾。GC Roots的对象包括:栈帧的局部变量表中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象等。

2、垃圾收集算法

        标记—清除算法:分为标记和清除两个阶段,先标记所有需要回收的对象,标记完成后,清除所有被标记的对象,也可以反过来,标记存活的对象然后清除所有未被标记的对象。标记清除算法最大的问题就是会造成内存空间产生大量不连续的内存碎片,导致分配大对象时无法找到足够的连续内存而提前触发垃圾收集动作。

        标记—复制算法:标记复制算法就是将原有的可用内存空间划分为大小相等的两块区域,每次只使用其中一块,当标记完不需要回收的对象后,先复制到另外一块上面,然后直接把原来的整个空间清理掉,这样虽然没有空间碎片问题,但是内存空间浪费太多了。

        标记—整理算法:标记过程不变,但标记过程完成后,先让所有存活的对象都向内存空间一端移动,然后直接清理边界以外的内存。也可以反过来把要回收的对象移动到一端,然后清理掉边界。

        分代—算法:分代算法就是将JVM 堆内存划分为不同的内存区域,各个区域采用不同的垃圾回收算法。例如对于存活对象少的新生代区域,比较适合采用复制算法。这样只需要复制少量对象,便可完成垃圾回收,并且还不会有内存碎片。而对于老年代这种存活对象多的区域,比较适合采用标记压缩算法或标记清除算法,这样不需要移动太多的内存对象。试想一下,如果没有采用分代算法,而在老年代中使用复制算法。在极端情况下,老年代对象的存活率可以达到100%,那么我们就需要复制这么多个对象到另外一个内存区域,这个工作量是非常庞大的。

        新生代的特点是存活对象少,适合采用复制算法。而复制算法的一种最简单实现便是折半内存使用,另一半备用。但在实际的 JVM 新生代划分中,而是分为:Eden 区域、from 区域、to 区域 这三个区域,根据IBM公司的研究表明,在新生代中的对象 98% 是朝生夕死的,所以并不需要按照1:1的比例来划分内存空间。

        所以在HotSpot虚拟机中,JVM 将内存划分为一块较大的Eden空间和两块较小的Survivor空间,其大小占比是8:1:1。当回收时,将Eden和Survivor中还存活的对象一次性复制到另外一块Survivor空间上,然后将存活的对象年龄+1,当满足进入老年代的条件时(默认年龄15),就放入老年代,最后清理掉Survivor和刚才用过的Eden空间。

        分区—算法:将整个堆空间划分成连续的不同小区间。每一个小区间都独立使用,独立回收,这种算法的好处是可以控制一次回收多少个区间,可以较好地控制 GC 时间。

3、算法实现细节

        根节点枚举:根节点枚举就是找出java程序中所有的GC Roots,迄今为止,所有的搜集器在 根节点枚举这一步骤都必须暂停用户线程。因为在整个枚举期间,根节点集合的对象引用关系是不允许改变的,就像在某一个时间点上。

        OopMap:当用户线程停止后,jvm并不需要一个不漏检查完所有执行上下文和全局的引用位置,而是使用一组OopMap的数据结构来到达这个目的,一旦类加载动作完成,虚拟机会把对象内什么偏移量上是什么类型的数据计算出来,在即时编译过程中,也会在特定位置记录下栈里和寄存器里那些位置是引用。这样收集器在扫描时就可以直接拿到这些信息,并不需要一个不漏的从方法区等GC Roots开始查找。

        安全点:   如果为每一条指令都生成对应的OopMap,那将会需要大量的存储空间,所以只有到特定的位置才会生成对应的OopMap,这些位置就是安全点,就是指Java线程执行到某个位置这时候JVM能够安全、可控的回收对象。那如何在垃圾收集时让所有线程都到安全点呢,主要有两种方式:抢先式中断和主动式中断。

        抢先式中断:不需要线程的执行代码去配合,在垃圾收集发生时,系统先把所有用户线程中断,如果有不在安全点上的线程,就恢复这条线程执行,让它一会再重新中断,直到跑到安全点上。

        主动式中断:设置一个标志位,各个线程执行过程时会不停地主动去轮询这个标志,一旦发现中断标志为真时就自己在最近的安全点上主动中断挂起。轮询标志的地方和安全点是重合的,

        安全区域:安全点完美的解决了如何进入GC问题,实际情况可能比这个更复杂,但是如果程序长时间不执行,比如线程调用的sleep方法,这时候程序无法响应JVM中断请求这时候线程无法到达安全点,显然JVM也不可能等待程序唤醒,这时候就需要安全区域了。

        安全区域是指一段代码片中,引用关系不会发生变化,在这个区域任何地方GC都是安全的,安全区域可以看做是安全点的一个扩展。线程执行到安全区域的代码时,首先标识自己进入了安全区域,这样GC时就不用管进入安全区域的线程了,线程要离开安全区域时就检查JVM是否完成了GC Roots枚举,如果完成就继续执行,如果没有完成就等待直到收到可以安全离开的信号。

        

        

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值