【JVM系列(二)】垃圾回收算法

垃圾回收算法

什么是垃圾?当一个对象没有被引用时就会被视为垃圾而回收。

内存泄露:申请内存空间而没有被正确释放,造成该块内存空间不可达;

内存溢出:存储数据超出了内存的额定大小,如栈溢出 stackoverflow。

1,垃圾回收的基本策略:

引用计数法:

通过计算器机制:对象引用一次+1,引用实效一次-1;为0则回收。但是这样无法解决循环依赖问题。

可达性分析法:

GC Roots所引用的对象为有限对象,这里的引用包括:

  • Java 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 本地方法栈中引用的对象
  • 方法区中常量引用的对象
  • 方法区中类静态属性引用的对象

由于不包括堆中的对象,所以此种方式不会涉及循环依赖问题。

引用类型:

  • 强引用:所有new出来的对象,只要引用存在,都不会被回收,即便JVM内存溢出;默认引用类型
  • 软引用:内存不足时会被回收;应用在高速缓存等
  • 弱引用(WeakReference):只要发动垃圾回收,都会被回收
  • 虚引用(Phantom Reference):随时被回收,设置目的是跟踪垃圾回收过程。

2,方法区内的垃圾回收:

我们知道,方法区内主要存放生命周期较长的类信息、常量、静态变量等。因此方法区主要回收的垃圾有两类:

  • 废弃常量:没有被常量或对象引用的;
  • 无用的类:
    • 该类的所有对象已经被清除;
    • 加载该类的 ClassLoader 已经被回收;
    • 该类的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

3,堆内的垃圾回收:

堆内不同的分区根据对象的生命周期不同会采用不同的回收策略,即分代收集策略:

3.1,新生代-复制算法-Minor GC

原理很简单,为了解决碎片问题,将内存分为等额的两份,垃圾回收时将一份分区内存活的对象复制到另一份分区,然后将原先分区全部回收即可。

但这样的缺点就是内存使用率不足,仅为原来的一半。为了改进此问题,可以将内存分为按8:1:1的形式分为Eden、From Survivor、To Survivor三个区,每次将Eden和一个Survivor分区的存活对象复制到另一个Survivor内,从而使得内存浪费率仅为10%。当然即便新生代的存活对象较少,当10%的内存不够时需要借助老年代进行分配担保。

3.2,老年代-标记清除和标记整理算法-Major GC

复制算法的高效性是建立在存活对象少、垃圾对象多的前提下的。这种情况在新生代经常发生,但是在老年代,更常见的情况是大部分对象都是存活对象。如果依然使用复制算法,由于存活对象较多,复制的成本也将很高。因此,基于老年代垃圾回收的特性,需要使用其他的算法。

3.3,标记清除算法

标记:遍历所有的 GC Roots,然后将所有 GC Roots 可达的对象标记为存活的对象

清除:遍历堆中对象,没有标记的即为垃圾;同时去掉标记对象的标记(不保证下次不被回收)。

不足:效率不高;标记清除导致空间内存碎片较多,不利于下次大对象分配而会导致提前触发垃圾回收机制。

3.4,标记整理算法

标记方式同样是遍历所有的GC Roots,然后将所有 GC Roots 可达的对象标记为存活的对象。随后整理策略是移动存活的对象,并按照内存地址排序,从而可以将末端地址的内存回收。注意在移动过程中需要暂停用户的应用程序(Stop The World,STW)

为了解决垃圾回收时系统应用线程挂起的对的情况,增量收集算法诞生。其基本思想是,垃圾回收线程和应用线程交替执行,以保持系统的稳定性。但是线程切换和上下文转换的消耗,会使得垃圾回收的总体成本上升,造成系统吞吐量的下降。

参考:https://doocs.gitee.io/jvm/#/docs/03-gc-algorithms

4,其他

对象在被视为垃圾回收之前,还有没有在挽回的余地呢?答案是有。Java 提供了一个对象的finalization机制,使用finalize()方法可以自定义对象在回收之前的处理逻辑,且该方法只会被调用一次。

我们知道对象的引用关系是有很大的不确定性的,当前行存活,下一行就有可能被回收。为此,GC的时间点和作用点都要有一定的要求。实际上,程序在执行时并非在所有地方都能停顿下来GC,能够停顿下来GC的地方我们称之为安全点(比如方法调用、循环跳转和异常跳转等,确保此处不会发生对象的引用关系变化)相应的,还有安全区的概念,即当前一段的代码片都是安全的。

补充:垃圾回收器

主要分类:

  • 并发式垃圾回收器与应用程序线程交替工作,以尽可能减少应用程序的停顿时间。

  • 独占式垃圾回收器(Stop the world)一旦运行,就停止应用程序中的所有用户线程,直到垃圾回收过程完全结束。

垃圾收集器的评价指标

  • 吞吐量:运行用户代码的时间占总运行时间的比例(总运行时间 = 程序的运行时间 + 内存回收的时间)

  • 垃圾收集开销:吞吐量的补数,垃圾收集所用时间与总运行时间的比例。

  • 暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间。

  • 收集频率:相对于应用程序的执行,收集操作发生的频率。

  • 内存占用:Java堆区所占的内存大小。

  • 快速:一个对象从诞生到被回收所经历的时间。

分析:高吞吐量和低暂停时间是相互矛盾的。垃圾一定的情况下,高吞吐量意味着会降低内部的回收频率,这就导致GC需要更多的暂停时间执行内存回收。相应的,如果为了低延迟,会频繁回收内存,容易导致吞吐量下降。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值