JVM 垃圾回收机制

垃圾回收
如何判断一个对象被使用
  • 引用技术算法(已废弃)

    引用计数是垃圾收集器中的早期策略。堆中每个对象实例都有一个引用计数,当一个对象被创建时,就将该对象实例分配给一个变量,该变量计数设置为1。每当有一个地方引用它时,计数器值就加1,引用失效时就减1。任何引用计数器为0的对象实例可以被当作垃圾收集。当一个对象实例被垃圾收集时,它引用的任何对象实例的引用计数器减1。

    缺点:

    很难解决对象之间相互循环引用的问题,就如果两个对象之间相互调用,这个地方相互调用会使得引用计数法始终认为有对象在引用当前对象,就一直计数值大于或等于1,也就无法通知GC收集器回收它们。但是实际的情况是这两个对象后面已经不再调用。

  • 可达性算法

    可达性算法是目前主流的虚拟机都采用的算法,程序把所有的引用关系看作一张图,从一个节点GC Roots开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点,无用的节点将会被判定为是可回收的对象。

垃圾回收基本算法

1.Mark-Sweep(标记-清除)算法

此算法执行分两阶段。

  1. 标记:遍历内存区域,对需要回收的对象打上标记。

  2. 清除:再次遍历内存,对已经标记过的内存进行回收。

  • 缺点:
    该算法最大的问题是内存碎片化严重,后续可能发生大对象不能找到可利用空间的问题。

效率问题:遍历了两次内存空间(第一次标记,第二次清除)。

空间问题:gc之后,会产生大量的内存碎片,随着gc次数增多,未使用的内存会被切割的越来越小。这些零碎的内存,足够小时,就不能存放连续的内存(如数组,数组是连续的内存空间),如数组10m,但是这一块内存只有1m,虽然这块内存未使用,但是也不能用来存放这个10m的数组。这种零碎的内存越来越多,则整体可以使用的内存越来越少。当再需要一块比较大的内存时,无法找到一块满足要求的,因而不得不再次出发GC。

2.Copying(复制)算法

此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。

垃圾回收时,遍历当前使用区域, 把正在使用中的对象复制到另外一个区域中。

  • 优点:此算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题。

  • 缺点:最大的缺点就是需要两倍内存空间。且存活对象增多的话,Copying算法的效率会大大降低。

jvm执行YGC时,清理伊甸园区和两个存活区就使用的复制算法。

老年代默认使用的是 标记-清除 算法,不可以使用复制算法,使用标记-清除算法,内存都可以被回收掉,但是不能清除碎片内存。

在这两个算法之上,又出现了 标记-整理(Mark-Compact)算法,结合了两种算法的优点。

3.Mark-Compact(标记-整理)算法(压缩法)

此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,

  1. 标记所有的存活对象

  2. 遍历整个堆,清除非存活的对象并且把存活对象“压缩”到堆的其中一 块,按顺序排放。

此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。

FGC,清理老年代时,期望使用标记-整理算法,

4.Generational Collection(分代收集)算法

分代收集算法是目前大部分JVM的垃圾收集器采用的算法。

这个算法并没有新的内容,只是根据对象的存活的时间的长短,将内存分为了新生代(Young Generation)和老年代(Tenured Generation),这样就可以针对不同的区域,采取对应的算法。新生代和老年代的默认比例是1:2,可以通过参数-XX:NewRatio修改,NewRatio默认值是2。如果NewRatio修改成3,那么年轻代与老年代比例就是1:3。

  • 新生代(年轻代):
    新生代的特点是每次垃圾回收时都有大量的对象需要被回收,大部分垃圾收集器对于新生代都采取复制算法,因为新生代中每次垃圾回收都要回收大部分对象,也就是说需要复制的操作次数较少,但是实际中并不是按照1:1的比例来划分新生代的空间的,一般来说是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚才使用过的Survivor空间,默认情况下Eden、From、To的比例是8:1:1。可以通过参数-XX:SurvivorRatio修改,SurvivorRatio默认值是8,如果SurvivorRatio修改成4,那么其比例就是4:1:1。

    • Eden(伊甸园区)

      所有新new的对象和数组,是在伊甸园区产生的,内存分配是在伊甸园区进行的。

      当伊甸园区内存被占满时,就不能再new对象,程序依赖对象,所以程序也就不能运行。java应用程序为了能继续运行,就要干掉那些不被引用的对象,对这些不被引用的对象进行垃圾回收。

      当伊甸园区被占满时,触发GC(垃圾回收 Garbage Collection)。

      GC分两步,第1步标记,对非存活对象进行标记。第2步清扫,将非存活对象从内存中干掉,释放内存,同时将存活的对象移到存活区中。

    • Survivor from 和 Survivor to

      Survivor区与Eden区相同都在Java堆的年轻代。Survivor区有两块,一块称为from区,另一块为to区,这两个区是相对的,在发生一次Minor GC后,from区就会和to区互换。在发生Minor GC时,Eden区和Survivor from区会把一些仍然存活的对象复制进Survivor to区,并清除内存。Survivor to区会把一些存活得足够旧的对象移至年老代。

      Minor GC(Young GC)

      1. 所有年轻代首先会在Eden区进行分配,当Eden区满了之后,会进行第一次Minor GC。
      2. 第一次GC后还存活的对象,会复制到ServivorFrom区,年龄+1,并清理缓存。
      3. 第二次,把 Eden 和 ServivorFrom 区域中存活的对象复制到 ServicorTo 区域(如果 ServicorTo 空间不足 ,存放老年区),并清空区域,同时年龄+1(如果对象年龄达到设置值(默认15) 就放到老年区)。
      4. ServicorTo 和 ServicorFrom 互换,原 ServicorTo 成为下一次 GC 时的 ServicorFrom区
      5. 以后GC以此类推。
      6. 当Survivor区域对象的年龄达到-XX:MaxTenuringThreshold设定的值(默认15),会将此对象移到老年代,同时清空他们在年轻代占用的内存空间。

      说明:Minor GC可能会引发STW,暂停其他用户的线程,需要等JVM垃圾回收结束后,用户线程才恢复运行。

  • 老年代:

    进入老年代原则

    • 大对象直接进入老年代

      在堆中分配的大对象直接挪到老年代

      大对象是指需要大量连续内存空间的对象,例如很长的字符串以及数组。

      虚拟机设置了一个-XX:PretenureSizeThreshold参数,有个默认值,令大于这个设置的对象直接在老年代分配,这个值是可以修改的。

      目的就是为了防止大对象在Eden空间和Survivor空间来回大量复制,大对象很容易把伊甸园区占满,导致YGC频繁。

    • 长期存活的对象进入老年代

      对象在Survivor区中每熬过一次YGC/Minor GC,年龄就加一,当他的年龄增加到一定程度,就会被移动到老年代(年龄值默认为15)。

      对象晋升老年代的阈值可以通过-XX:MaxTenuringThreshold设置。

    • 动态年龄判断

      为了更好的适应不同程序的内存状况,虚拟机并不是永远要求对象的年龄必须达到MaxTenuringThreshold才会晋升到老年代。

      如果在Survivor空间中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需达到MaxTensuringThreshold的要求年龄。

      (相同age对象大小之和>1/2存活区(即s0或者s1)则所有>=age的对象就会进入老年代)

      举个栗子:

      比如:MaxTenuringThreshold 为15,Survivor内存大小为100M。age 为1的,所有对象大小之和为10M。age 为5的,所有对象大小之和为51M。age为6的,所有对象大小之和为5M.

      因为age 为5的对象所占内存之和已经超过了Survivor空间的一半,所以age为5,和age大于5的对象,都要移到老年代(没有age达到15的限制)

    • 一次Young GC时数据放到存活区,但是存活区满了导致放不下去此时直接进入老年代(尽可能避免这种情况)

      尽可能避免这种情况,如果对象直接从Eden区到老年代,那么存活区就没有什么存在的意义了,之所以设置存活区,就是为了将没有引用的对象更早的回收掉,将内存腾出来。

    Major GC

    主要是清理老年代:MajorGC的速度一般会比Minor GC慢 10倍以上,出现了Major GC 经常会伴随至少一次的Minor GC(但非绝对的,在Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程),也就是说老年代空间不足时,会先尝试触发Minor GC 如果之后空间还不足,触发Major GC。如果Major GC 后,内存还不足,oom错误。

    Full GC

    主要是清理整个堆空间—包括年轻代和老年代。

    触发机制:

    • 调用System.gc(),系统建议Full GC ,不一定执行
    • 方法区空间不足:当永久代/元数据空间满时也会引发Full GC,会导致Class、Method元信息的卸载。
    • 通过Minor GC后进入老年代的平均大小大于老年代的可用内存。
    • 由Eden区、survivor space1(From Space)区向survivor space2(To Space)区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值