当前位置:   article > 正文

JVM(java虚拟机)垃圾回收机制 超详细_jvm垃圾回收机制

jvm垃圾回收机制

JVM垃圾回收机制详解



前言

什么是垃圾

垃圾其实是指那些没有被引用或者没有被任何指向的对象,那么这个这个对象就会被称为垃圾

如果我们不及时的将垃圾进行回收,就有可能会造成内存泄漏,因为这些垃圾一直占用着我们的内存直达我们的应用程序结束,并且其他的对象无法进来到内存当中。

Java的垃圾回收

- 垃圾回收(GC)为什么会被需要呢
首先得话,任何高级语言我们如果我们不进行垃圾回收的话,我们的内存迟早是会被消耗完的,因为我们要不断地给新的对象分配空间,所以我们需要GC帮助我们把那些不需要或者用不上的对象给回收,产生新的内存给我新对象使用


在java中拥有自动的内存管理,无需开发人员手动的参与内存的分配与回收,这样的就降低了内存泄漏和溢出的风险,从回收角度来看,java的堆是回收的重点,从我们的次数来看的话,频繁收集新生代,较少回收老年代,基本不会动元空间

一、垃圾回收的算法

垃圾标记阶段

因为当我们需要垃圾回收的时候,我们要知道哪些是垃圾可以被回收。
一般2种方式 引用计数算法和可达性分析算法


  • 引用计数算法

对每一个对象保存一个整型的引用计数器属性,用于记录对象被引用的情况
优点: 实现简单,垃圾对象便于识别,判断效率高,回收没有延迟性
缺点:

 1.需要单独的字段作为存储计数器,这样的做法会增加内存的开销
 2.每一次的赋值都需要更新计数器,会增加我们的时间开销
 3.无法处理循环引用的问题
  • 1
  • 2
  • 3
  • 可达性分析算法

特点:
对于引用计数算法来说,不仅仅是简单高效,而且它解决了循环引用的问题,防止了内存泄漏
它是一个追踪性垃圾收集器

 以对象集合为起点(GC Root)为起始点,按从上到下的方式搜索跟踪目标对象,看看能否到达,
 在内存当中根对象和内存中对象,直接或者间接的连接,被称为引用链,如果没有的话,说明这个
 垃圾对象(死亡对象),将其清除
  • 1
  • 2
  • 3

那什么对象可以被称之为GC Roots对象呢

  • 虚拟机栈中的引用对象,比如java线程中,被调用的引用参数,局部变量
  • 本地方法栈中的引用对象
  • 方法区中的类静态属性引用对象
  • 方法区中的常量引用对象 比如,stringtable(字符串常量池)的引用
  • 所有被同步锁synchronized中持有的对象
  • java虚拟机内部的引用。基本数据类型对应的Class对象,系统类加载器

垃圾清除算法

当成功区分出内存中存活对象和死亡对象后,GC接下来的任务就是执行垃圾回收、释放掉垃圾对象所占用的内存,以便有足够的可用空间为新对象分配内存。
目前在JVM中比较常见的三种垃圾回收算法是:
1.标记 - 清楚算法
标记-清除(Mark - Sweep)算法是最基础的收集算法,它分为“标记”和“清除”两个阶段:

  • 标记:从GC Roots开始遍历,标记所有被引用的对象。一般是在对象头中记录是否是可达对象

  • 清除:对堆内存从头到尾遍历,如果发现某个对象的对象头中没有标记为可达对象,则将其回收

  • 优点:

    不需要进行对象的移动,并且仅对不存活的对象进行处理,在存活对象比较多的情况下极为高效

  • 缺点:
    标记和清除过程的效率都不算高
    这种方法需要使用一个空闲列表(空闲列表记录哪些内存是没有被占用状态,空闲的)来记录所有的空闲区域以及大小,对空闲列表的管理会增加分配对象时的工作量
    标记清除后会产生大量不连续的内存碎片

2.复制算法
核心思想:
将内存空间分为两块,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除正在使用的内存块中的对象,交换两个内存的角色,最后完成垃圾回收

对于这种算法来说,如果存活的对象过多的话则要执行较多的复制操作,效率会变低,因此它适合存活率较低的情况。事实上在年轻代中就是使用的复制算法。

  • 优点:
    没有标记和清除的过程,实现简单,运行高效
    复制过去以后保证空间的连续性,不会出现“碎片”问题

  • 缺点:
    需要两倍的内存空间,比较浪费
    如果存活对象较多,那么复制操作就比较多,效率相对会降低

3.标记-整理算法
标记-整理分为“标记”和“整理”两个阶段:

标记:和标记清除算法一样,从GC Roots开始标记所有被引用的对象
整理:将所有的存活对象压缩到内存的一端,按顺序排放。之后清理外边界的空间(清理垃圾)

标记-整理算法的最终效果等同于标记-清除算法执行后,再进行一次内存碎片整理,因此也可以把它称为标记-清除-压缩算法

可以看到,标记的存活对象将会被整理,按照内存地址依次排序。如此一来,当我们需要给新对象分配内存时,JVM只需要持有一个内存的起始地址即可,这比维护一个空闲列表显然少了许多开销

  • 优点:

    消除了标记-清除算法中,内存区域分散的缺点(内存碎片)

  • 缺点:

    移动对象的同时,如果对象被其他对象引用,还需要调整引用的地址
    移动过程中,需要全程暂停用户应用程序。即STW

分代收集

  • 新生代:几乎所有新生成的对象首先都是放在年轻代的(对象过大会被分配到老年代)。新生代内存按照 8:1:1 的比例分为一个 Eden(伊甸园区) 和两个 Survivor(幸存区),幸存区一个称为“From”区、一个称为“To”区,名字是动态的(谁空谁是“to”)。当新对象生成,Eden存满了空间不足,则会发起一次 Minor GC。回收时先将 Eden 区存活对象复制到一个 From区,然后清空 Eden 区。再次触发Minor GC时(Eden园满了才会触发),则将 Eden 区和 From区的存活对象复制到另一个 To区,然后清空 Eden 和这个 From区,此时 From区是空的,然后将 From区和 To区交换(谁空谁是To区), 如此往复。当 To区不足以存放 Eden 和 From的存活对象时,就将存活对象直接存放到老年代。当对象在 Survivor 区躲过一次 GC 的话,其对象年龄便会加 1,默认情况下,如果对象年龄达到 15 岁,就会移动到老年代中。若是老年代也满了就会触发一次 Full GC,也就是新生代、老年代都进行回收。新生代大小可以由-Xmn来控制,也可以用-XX:SurvivorRatio来控制 Eden 和 Survivor 的比例。(垃圾清除算法用的是复制算法)
  • 老年代:在新生代中经历了 N 次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。内存比新生代也大很多(大概比例是 1:2),当老年代内存满时触发 Major GC 即 Full GC,Full GC 发生频率比较低,老年代对象存活时间比较长,存活率高。一般来说,大对象会被直接分配到老年代。所谓的大对象是指需要大量连续存储空间的对象,最常见的一种大对象就是大数组。当然分配的规则并不是百分之百固定的,这要取决于当前使用的是哪种垃圾收集器组合和 JVM 的相关参数。(垃圾清除算法用的是 标记清除或者标记整理)
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/爱喝兽奶帝天荒/article/detail/939622
推荐阅读
相关标签
  

闽ICP备14008679号