GC算法

在这里插入图片描述

JVM在进行GC时,并非每次都对上面三个内存区域一起回收的,大部分时候回收的都是指新生代。
因此GC按照回收的区域又分了两种类型,一种是普通GC(minor GC),一种是全局GC(major GC or Full GC)

Minor GC 和Full GC的区别

普通GC(minor GC):只针对新生代区域的GC,指发生在新生代的垃圾收集动作,因为大多数Java对象存活率都不高,所以Minor GC非常频繁,一般回收速度也比较快。
全局GC(major GC or Full GC):指发生在老年代的垃圾收集动作,出现了Major GC,经常会伴随至少一次的Minor GC(但并不是绝对的)。Major GC的速度一般要比Minor GC慢上10倍以上

四大算法

复制算法(Copying)

年轻代中使用到Minor GC 是采用的复制算法

将内存空间分为两块相同的存储空间,每次只使用一块,GC时,将正在使用的内存中的存活对象复制到另一块存储空间中,然后清除正在使用的空间的所有对象

现在的商业虚拟机都采用复制收集算法来回收新生代,有研究表明,新生代中的对象98%是朝生夕死的,所以并不需要按照1:1的比例来划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中的一块Survivor。当回收时,将Eden和Survivor中还存活着的对象一次性地拷贝到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8:1,也就是每次新生代中可用内存空间为整个新生代容量的90%(80%+10%),只有10%的内存是会被“浪费”的。

当然,并不能保证每次回收都只有10%的对象存活,当Survivor空间不够用时,需要依赖其他内存(这里指老年代)进行分配担保(Handle Promotion)。即如果另外一块Survivor空间没有足够的空间存放上一次新生代收集下来的存活对象,这些对象将直接通过分配担保机制进入老年代。

GC过程:先复制,再清除

优点:存活对象相对少时,效率很高(因为需要复制的对象少),存活对象复制到另一空间时,解决了空间碎片问题

缺点:系统内存只能使用一半的内存空间,而且如果存活对象相对多的话,比较耗时

复制算法比较适用于新生代。因为在新生代中,垃圾对象通常会多于存活对象

标记清除算法(Mark-Sweep)

标记所有的可达对象(存在引用的对象),则未被标记的对象就是不存在引用的垃圾对象,GC时清除所有未被标记的对象

GC过程:标记清除法的GC时经历标记 + 清除两个过程,先标记,后清除

优点:只存在循环引用不存在其他引用的对象不会被标记,解决了循环引用问题

缺点:可能会产生空间碎片(不连续的内存空间),不连续的内存空间在内存分配时的工作效率低于连续的内存空间,尤其是对大对象的的内存分配

标记整理算法(Mark-Compact)(针对老年代)

复制收集算法在对象存活率较高时就需要执行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用复制收集算法。
根据老年代的特点提出了“标记-整理”算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

标记-整理的步骤:
标记阶段
整理阶段:移动存活对象,同时更新存活对象中所有指向被移动对象的指针

整理的顺序

不同算法中,堆遍历的次数,整理的顺序,对象的迁移方式都有所不同。而整理顺序又会影响到程序的局部性。主要有以下3种顺序:

  1. 任意顺序:对象的移动方式和它们初始的对象排列及引用关系无关

任意顺序整理实现简单,且执行速度快,但任意顺序可能会将原本相邻的对象打乱到不同的高速缓存行或者是虚拟内存页中,会降低赋值器的局部性。任意顺序算法只能处理单一大小的对象,或者针对大小不同的对象需要分批处理;

  1. 线性顺序:将具有关联关系的对象排列在一起
  2. 滑动顺序:将对象“滑动”到堆的一端,从而“挤出”垃圾,可以保持对象在堆中原有的顺序

所有现代的标记-整理回收器均使用滑动整理,它不会改变对象的相对顺序,也就不会影响赋值器的空间局部性。复制式回收器甚至可以通过改变对象布局的方式,将对象与其父节点或者兄弟节点排列的更近以提高赋值器的空间局部性。

整理算法的限制,如整理过程需要2次或者3次遍历堆空间;对象头部可能需要一个额外的槽来保存迁移的信息。

部分整理算法:

1. 双指针回收算法:实现简单且速度快,但会打乱对象的原有布局
2. Lisp2算法(滑动回收算法):需要在对象头用一个额外的槽来保存迁移完的地址
3. 引线整理算法:可以在不引入额外空间开销的情况下实现滑动整理,但需要2次遍历堆,且遍历成本较高
4. 单次遍历算法:滑动回收,实时计算出对象的转发地址而不需要额外的开销

GC过程:分为标记+压缩(移动)+清除三个步骤

优点:解决了标记清除法带来的空间碎片问题,又不需要折损可使用空间(复制算法折损了可使用空间)

缺点:移动对象需要成本 消耗时间长

分代收集算法(Generational Collection)

当前商业虚拟机的垃圾收集都采用“分代收集”算法,这种算法并无新的方法,只是根据对象的存活周期的不同将内存划分为几块,一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或“标记-整理”算法来进行回收。

算法使用情况

内存效率:复制算法>标记清除算法>标记整理算法(此处的效率只是简单的对比时间复杂度,实际情况不一定如此)。
内存整齐度:复制算法=标记整理算法>标记清除算法。
内存利用率:标记整理算法=标记清除算法>复制算法。

如何判断对象是否可以被回收

引用计数法

每一个对象都有一个counter,只要有任何一个对象引用了该对象,则其counter加1,当引用失效时,counter减1,当counter为0时,对象不存在任何引用,在GC时被清除

GC过程:counter在每次引用生效和失效时进行加减法操作,并判断是否为0,是则清除

优点:思想和实现都很简单(只需要为每一个对象配备一个整型的计数器)

缺点

  1. 无法处理循环引用问题:比如对象A和对象B互相引用,但是不存在其他对象引用A和B,此时A和B属于不可达的对象,即垃圾对象,但是counter无法识别此类垃圾对象间的互相引用,从而引起内存泄露,不能GC
  2. counter要求在每次引用生效和失效时进行加减法操作,在一定程度上影响系统性能

可达对象和不可达对象:通过根对象的进行引用搜索,最终可以到达的对象为可达对象,可达对象即为存在引用的对象,反之,则为不可达对象

内存泄漏:内存空间使用完毕之后未回收

注意:Java虚拟机不使用引用计数法GC

可达性分析法

可达性分析法是通过以所有的“GC Roots”对象为出发点,如果无法通过GC Roots的引用追踪到的对象,那我们认为这些对象就不会再次被使用了,现在主流的程序语言都是通过可达性分析法来判断对象是否存活的。

可达性分析法

哪些对象对象我们称之为"GC Roots"对象呢? 当然普通的对象肯定是不行的,如果要作为GC Roots 对象那么它自身肯定得满足一个条件,那就是他自己一定在很长一段时间内都不会被GC 回收掉。那么只有满足这个条件的对象才可能作为GC Roots了,GC Roots的类型大致如下:

  1. 虚拟机栈中的本地变量所引用的对象。

  2. 方法区中静态属性引用的对象。

  3. 方法区中常量引用的对象。

  4. 本地方法中(Native方法)引用的对象。

  5. 虚拟机内部的引用对象(类记载器、基本数据对应的Class对象,异常对象)。

  6. 所有被同步锁(Synchronnized)持有的对象。

  7. 描述虚拟机内部情况的对象(如 JMXBean、JVMTI中注册的回调、本地缓存代码)。

  8. 垃圾搜集器所引用的对象。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值