JVM 垃圾回收(2)《垃圾回收算法/分代回收》

参考

JVM 垃圾回收(1)《根对象/四种引用》

垃圾回收算法

标记清除

判断一个对象是否是垃圾,就是沿着GC Root的引用链去找,扫描整个堆对象的过程中,如果发现这个对象确实被引用了,那么他需要保留,但比如有个对象没有被GC Root直接或间接的引用,那么他就可以当成是垃圾进行回收。

标记清除算法分两个阶段,第一个阶段是先标记,看看哪些对象是垃圾,第二个步骤就是做清除,即把垃圾他所占用的空间给他释放。

这里可能会造成一个小误区,有些人说释放是不是意味着我要把整个这个对象的内存每个字节进行清零操作呢,其实不会的,他只需要把这个对象所占用的内存的起始、结束的地址记录下来,放在一个叫空闲的地址列表里就可以,下次再分配新对象时,就到这个空闲地址列表中去找看有没有一块儿足够的空间容纳我的新对象,如果有就会进行内存分配,并不会把这个他所占用的内存做一个清零的处理。

标记清除算法的优点是垃圾清理的速度快,缺点是内存空间不连续,从而产生内存碎片(因为被干掉的内存空间大小各不一,所以以后新的对象需要分配空间时,还得一个一个去看够不够,不够的话装不住,够了才能装进去,这些一个个内存起始~结束的空间都小的话,就不能把这个对象分配进去,就会出现内存溢出,这些内存中一个个不连续的内存空间区域称为内存碎片)。

在这里插入图片描述

标记整理

和标记清除一样,标记整理的第一个阶段也是对垃圾对象进行标记,区别主要在第二个步骤,即整理。所谓的整理就是避免之前标记清除时的内存碎片的问题,他就会在清除的过程中,会把可用的对象向前给他移动,这样的话让内存更为紧凑,这就是整理的过程。整理之后,就能发现内存变的更紧凑了,即连续的空间就更多了,这样就不会造成内存碎片。

标记清除算法的优点是没有内存碎片,缺点是由于整理牵扯到了对象的移动,必然这个效率就会变得较低,如果那些局部变量或对象引用了这个,那么还得改变那些变量或对象的引用地址,因为内存地址变了,所以涉及的工作就比较多一些,牵扯到内存区块的拷贝移动,牵扯到所有引用的地址加以改变。所以速度较慢,因为干的活儿比较多。
在这里插入图片描述

复制

复制算法比较特殊,他把内存区域划分成了大小相等的两块儿区域,左边区域称之为FROM,右边区域称之为TO,其中TO这个区域始终空闲着,即里面一个对象都没有。

复制算法做垃圾回收,也是首先做一次标记(在FROM区域),就找到那些不被根对象引用的对象标记为垃圾,然后在FROM区域这些存活的对象内存空间复制到TO区域中,复制的过程中就会完成碎片的整理,因此也不会产生碎片,等复制完成后,可以看到FROM区域全是垃圾,所以一下子给予清空,并且之后交换FROM和TO他两的位置,即原来的TO变成了FROM,原来的FROM变成了TO,所以TO
总是空闲的一块儿。

可以看到复制算法的优点也是不会产生内存碎片,但缺点是复制算法会占用双倍的内存空间。
在这里插入图片描述
在这里插入图片描述

整理

(1)标记清除算法 Mark Sweep

  • 速度较快
  • 会造成内存碎片

(2)标记整理算法 Mark Compact

  • 速度慢
  • 没有内存碎片

(3)复制算法 Copy

  • 不会内存碎片
  • 需要占用双倍内存空间

分代回收

实际的JVM不会单独采用其中的一种算法,他都是结合这前面的三种算法,让他们协同工作。具体的一个实现就是虚拟机的叫分代的垃圾回收机制。

他把我们整个堆内存划分成两块儿,一个是新生代,一个叫老年代。新生代又进一步划分了三个小的区域,分别叫伊甸园幸存区From幸存区To

那么,为什么要做这样的区域划分呢?主要是因为Java中有的对象他可能需要长时间使用,这种长时间使用的对象放在老年代当中,而那些用完了就可以丢弃的对象,把他放在新生代当中。这样的话就能对这个对象生命周期的不同特点,进行不同的垃圾回收策略。老年代的垃圾回收就很久才发生一次,而新生代的垃圾回收就发生的比较频繁。

当我们创建一个新的对象时,那么默认情况下他就会放在伊甸园的一块儿空间。接下来,可能会有更多的对象被创建,他们也会默认被分配到伊甸园当中。那么,伊甸园逐渐的就会被占满,此时,当再创建一个对象时发现伊甸园空间不够了,这个时候就会触发一次垃圾回收,新生代的垃圾回收叫Minor GC,即小的一次垃圾回收。

Minor GC触发以后,他就会采用可达性分析算法沿着GC Root引用链去找看这些对象是有用还是可以作为垃圾记性一次标记的动作,标记成功了后他就会采用一种“复制算法”,把存活的对象复制到幸存区To中,复制过去之后,还会让幸存的对象他的寿命加1(刚开始他们的寿命都是0,现在他幸存了,经历了一次垃圾回收,还幸存下来不死,那他们的寿命就会加1),而没能幸存的伊甸园里的那些对象就会被回收掉了,当然,做完一次复制动作之后,就会交换幸存区from和幸存区to的位置,所以那些存活的现在都在from里了,这是第一次垃圾回收产生的效果。

第一次垃圾回收之后,伊甸园的空间又充足了,所以可以继续向伊甸园放入对象,比如刚才那个放不下的对象也放进去了,类似的又分配一大堆对象进去,经过一段时间后,分配一个对象时,发现伊甸园空间又不够了,这时候再触发第二次Minor GC(网友1:在每次minorGC之前to区一定是空的)。

第二次垃圾回收时,除了伊甸园中存活的对象找到以外,还要去看幸存区中有没有需要继续存活的对象,幸存区里的对象也不是说第一次垃圾回收时不死,第二次也仍然不死,因为也许第二次回收时,他已经没用了。所以第二次垃圾回收时,他就会把伊甸园中幸存的对象放在to里去,并且寿命加1,比如幸存区from中有个对象又存活了,就把它再放入到to中,这时候他的寿命就变成了2,伊甸园里剩下的垃圾对象就要回收掉,又把伊甸园空出来了。当然幸存区里面的垃圾也会给他清理掉,就是刚才在from中没能存活到to的对象们。这样的话,新的对象又可以放入到伊甸园,当然,幸存区的from和to的位置也要交换。这是第二次minor GC。

幸存区中的对象他不会永远在幸存区中呆着,当他的寿命超过了阈值(最大15),即只要你经历了15次垃圾回收你还活着,那说明你这个对象价值比较高,经常在使用,那没必要把你一直在幸存区里留着,因为你在幸存区留着,以后我再垃圾回收,还是不能回收你,假设幸存区里的某个对象寿命已经超过阈值了,就会把他晋升到老年代去。因为老年代的垃圾回收频率较低,所以不会轻易的把他回收掉,那显然这种价值较高的对象就会从幸存区把他晋升到老年代去。

这就是minor GC的垃圾回收流程。

那将来假如老年代里的对象变多了,那么老年代也有可能会快放满了,比如有这么个情况,新生代放的东西也很多,老年代放的东西也快满了,两个几乎全满了,这时候比如又来了一个新对象,比如他无法放入到新生代伊甸园中(内存不够),幸存区也放不下了,而老年代那里也放不下了(网友1:有内存担保机制,大对象会直接分配到了老年代),这时候就触发一次Full GC,一般来说这种垃圾回收动作都会在空间不足时触发。

fullGC的含义是,当老年代的空间不足,他当然会尝试先回收新生代,当新生代内存依然不够的时候,他就会触发full GC来触发一个老年代的垃圾回收,老年代的垃圾回收就会做从新生到老年整个堆的垃圾清理。

这就是垃圾回收过程中基本的流程。

要点整理

  • 新对象首先分配在伊甸园区域
  • 新生代空间不足时,触发minor GC(网友1:应该是伊甸园区不够时触发minorGC。老年代不足时触发FullGC。),伊甸园和幸存区from中存活的对象使用复制算法复制到幸存区to中,然后让存活的对象年龄加1,并且交换from和to(当然,交换之前伊甸园被标记的垃圾和from中的垃圾都会被回收掉了)。
  • minor gc时,会引发一次stop the world(网友1:fullGC才会stop the world吧?网友2:按理只要是发生垃圾回收都会stop the world)。即在我发生垃圾回收时,我必须暂停其他的用户线程,由我的垃圾回收线程来完成垃圾回收的动作,当我把这些对象都从伊甸园、幸存区from中拷贝到幸存区to之后,即垃圾回收的动作都做完了,其他的用户线程才能继续运行。(为什么垃圾回收时,把用户线程比如用户执行的方法都暂停掉呢?这是因为垃圾回收的过程中,牵扯到了对象的复制,即他的对象地址会发生改变,在这种情况下,如果多个线程大家都在同时运行,这样的话就会造成一个混乱,比如对象都移动了,其他的线程访问这个对象时地址都变了,根据原来的地址找是找不到的,所以minorGC他在工作的过程中,都会引发stop the world,即暂停其他用户的线程,让我们的垃圾回收线程先工作,等回收完了,其他的用户线程再恢复运行。只不过minor GC他的暂停时间非常短,因为因为新生代本身这个大部分对象都是垃圾,都会被回收掉,复制的对象只有很少的一部分,所以这个暂停时间包括标记、复制的时间并不长,所以新生代触发的minorGC他的STW的时间较短。
  • 当对象寿命超过阈值时,就会晋升至老年代,最大寿命是15次(这个寿命是保存在每个对象的对象头中,其中纯寿命的部分是4个bit,即4位,也就是4个0101,即4位最大能表示的数字是1111,转换成十进制就是15,所以他的最大寿命也就记录到15。为什么不能设的更大一些呢,一是没有意义,二是因为对象头的那个都比较金贵,每个byte都有各自的用途,不能把所有的控件都留给我存储寿命。在不同的垃圾回收器,他对这个阈值也不一样,有的时候当新生代空间紧张时,也许你这个对象寿命还没有达到15,他也可能会提前晋升到老年代去,所以15只是一个最大的阈值,并不是说他一定要等到15岁才晋升到老年代)。
  • 当老年代空间不足,先尝试做一次minorGC,看新生代空间释放后能不能分配我的对象,结果发现当minorGC以后,空间还不足,这时候就会触发fullGC(网友1:这里明显是失误写错了,应该是当新生代空间不足?网友2:Java6 update24之后规定&#x
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值