美文网首页
JVM垃圾回收GC算法

JVM垃圾回收GC算法

作者: 丿灬尘埃 | 来源:发表于2022-03-16 20:05 被阅读0次

    一. 什么叫做垃圾回收

    垃圾回收(Garbage Collection, GC)
    简单来说,就是把不在使用的对象清除掉,释放内存。给其他新生儿腾地方。
    Java会对内存进行自动分配与回收管理,使上层业务更加安全,方便地使用内存实现程序逻辑
    在不同的JVM实现及不同的回收机制中,堆内存的划分方式是不一样的

    二.静态/动态内存分配与回收

    正所谓不了解内存的分配与回收不足以了解java堆内存的回收

    2.1静态内存分配和回收

    定义: 在程序开始运行时由编译器分配的内存
    在被编译时就已经能够确定需要的空间,当程序被加载时系统把内存一次性分配给它,这些内存不会在程序执行时发生变化,直到程序执行结束时才回收内存.

    • 包括原生数据类型及对象的引用

    • 这些静态内存空间在栈上分配,方法运行结束,对应的栈帧撤销,内存空间被回收.

    • 每个栈帧中的本地变量表都是在类被加载的时候就确定的,每一个栈帧中分配多少内存,基本上是在类结构确定时就已知了,因此这几块区域内存分配和回收都具备确定性,就不需要过多考虑回收问题了

    2.2动态内存分配和回收

    在程序执行时才知道要分配的存储空间大小,对象何时被回收也是不确定的,只有等到该对象不再使用才会被回收.

    堆和方法区的内存回收具有不确定性,因此垃圾收集器在回收堆和方法区内存的时候花了一点心思.

    三.java堆内存回收

    3.1 如何判断对象要被回收呢?

    GC是如何判断对象是否可以被回收的呢?
    为了判断对象是否存活,JVM引入了GC Roots
    如果一个对象与GC Roots之间没有直接或间接的引用关系,比如某个失去任何引用的对象,或者两个互相环岛状循环引用的对象等,判决这些对象“死缓”,是可以被回收的

    再回收之前要做的一件事情就是,判断哪些是无效对象(一个对象不被任何对象或变量引用)
    判断方式有以下2种方式:

    • 引用计数法 (Reference Counting) 每个对象都有一个整型的计数器,当这个对象被一个变量或对象引用时,该计数器加一;当该引用失效时,计数器值减一.当计数器为0时,就认为该对象是无效对象.

    • 可达性分析法 (Reachability Analysis) 所有和GC Roots直接或间接关联的对象都是有效对象,和GC Roots没有关联的对象就是无效对象.说白了点就是和GC Roots有关系,你就是有效的,没关系,那么对不起你就是个无效的,我就要收拾你


    两者对比
    引用计数法虽然简单,但存在无法解决对象之间相互循环引用的严重问题,且伴随加减法操作的性能影响.
    因此,目前主流语言均使用可达性分析方法来判断对象是否有效.


    此处应该做下GC Roots的解释,上菜~

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

    GC Roots并不包括堆中对象所引用的对象!这样就不会出现循环引用.

    四.无效对象回收-过程

    聊完了什么样的对象会被回收,该聊聊回收的过程是怎么样的了
    刚刚说了2种判断方式,那么java采用的是可达性分析法,对于用可达性分析法筛选出来的无效对象,并不是立即清除的,而是给了他一次改过自新的机会

    在JVM的垃圾回收器来看。堆区中的每个对象都肯能处于以下三个状态之一:

    可触及状态:当一个对象被创建后,只要程序中还有引用变量引用该对象,那么它就始终处于可触及状态。

    可复活状态:当程序不再有任何引用变量引用对象时,它就进入可复活状态。该状态的对象,垃圾回收器会准备释放它占用的内存,在释放前,会调用它的finalize()方法,这些finalize()方法有可能使对象重新转到可触及状态。

    不可触及状态:当JVM执行完所有的可复活状态的finalize()方法后,假如这些方法都没有使对象转到可触及状态。那么该对象就进入不可触及状态。只有当对象处于不可触及状态时,垃圾回收器才会真正回收它占用的内存

    垃圾回收的时间

    当一个对象处于可复活状态时,垃圾回收线程执行它的finalize()方法,任何使它转到不可触及状态,任何回收它占用的内存,这对于程序来说都是透明的。程序只能决定一个对象任何不再被任何引用变量引用,使得它成为可以被回收的垃圾。

    五.垃圾回收算法

    5.1 标记 - 清除

    1. 标记需要回收的内存
    2. 将上述标记的对象进行统一回收


      image.png

    劣势

    1. 执行效率不稳定:
      从上述也看出来,这种算法是分2步的,先标记后清除,假如对于有大量对象要回收时,该算法会随着对象的增加效率降低

    2.内存碎片化:
    从图片中可以看到,当对象被回收后,未使用的区域呈现无规则随机分布,那么伴随而来的就是内存碎片化。同时当有大对象要实例化时,由于内存碎片严重无法分配连续大内存空间,就会在一次导致垃圾回收。

    5.2 标记 - 整理

    由于标记 - 清除方式会造成内存碎片,继而提出了另一种算法 -- 标记 - 整理
    大概意思为:
    1.标记需要回收的对象

    1. 将存活的对象向空间内存的一段移动【此时界限就很明确了,同时内存是连续的】
    2. 清理掉无效标记对象


      image.png

    劣势
    标记整理算法,在老年代区域中每次回收都有大量的对象存活,并且移动对象时需要更新所有对象的引用,所以会造成比较大的系统开销,而且对象移动操作必须全程暂停用户应用程序(Stop The Word)才能进行。

    5.3 复制算法

    为了能够并行地标记和整理将空间分为两块,每次只激活其中一块,垃圾回收时只需把存活的对象复制到另一块未激活空间上,将未激活空间标记为已激活,将已激活空间标记为未激活,然后清除原空间中的原对象

    将内存分成大小相等两份,只将数据存储在其中一块上
    1.当需要回收时,首先标记废弃数据

    1. 然后将有用数据复制到另一块内存

    2. 最后将第一块内存空间全部清除


      image.png

    劣势

    1. 将内存分为2部分后,内存空间减少,空间利用率缩减
    2. 如果对象存活太多,复制过程中大量存活对象的复制会造成效率低下,开销大;相反,存活对象较少,不失为一种very good的算法

    对于以上2点,其实该算法做了比较完善的优化的

    优化

    堆内存空间分为较大的Eden两块较小的Survivor,每次只使用Eden和Survivor区的一块。这种情形下的“ Mark-Copy"减少了内存空间的浪费。“Mark-Copy”现作为主流的YGC算法进行新生代的垃圾回收。
    在新生代中,由于大量对象都是"朝生夕死",也就是一次垃圾收集后只有少量对象存活
    因此我们可以将内存划分成三块

    • Eden、Survior1、Survior2
    • 内存大小: Eden : Survior : Survior2 = 8:1:1

    分配内存时,只使用Eden和一块Survior1.

    • 当发现Eden+Survior1的内存即将满时,JVM会发起一次Minor GC,清除掉废弃的对象,
    • 并将所有存活下来的对象复制到另一块Survior2中.
    • 接下来就使用Survior2+Eden进行内存分配

    通过这种方式,只需要浪费10%的内存空间即可实现带有压缩功能的垃圾收集方法,避免了内存碎片的问题.

    一般情况下,经过15次分配后就进入了老年代区域

    5.4 分代收集算法

    根据对象存活周期的不同将Java堆划分为老年代和新生代,根据各个年代的特点使用最佳的收集算法.

    • 老年代中对象存活率高,无额外空间对其分配担保,必须使用"标记-清除"或"标记-压缩"算法
    • 新生代中存放"朝生夕死"的对象,用复制算法,只需要付出少量存活对象的复制成本,就可完成收集

    相关文章

      网友评论

          本文标题:JVM垃圾回收GC算法

          本文链接:https://www.haomeiwen.com/subject/tdbydrtx.html