垃圾收集算法
1、标记-清除
标记要回收的对象,然后清除。
不足:
- 标记和清除过程效率都不高
- 会产生大量不连续的内存碎片,导致无法给对象分配内存。
2、标记整理
让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
优点:
-
不会产生内存碎片
不足:
-
需要移动大量对象,处理效率比较低。
3、复制
将内存划分为大小相等的两块,每次只使用其中一块,当这一块内存用完了就将还存活的对象复制到另一面上,然后再把使用过的内存空间进行一次清理。
主要不足是只使用了内存的一半。
现在商业虚拟机都采用这种收集算法回收新生代,但是并不是划分为大小相等的两块,而是一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Surviver。在回收时,将Eden和Surviver中还存活着的对象全部复制到另一块Surviver上,最后清理Eden和使用过的那一块Surviver。
HotSpot虚拟机的Eden和Surviver大小比例默认为8:1,保证了内存使用率达到90%。如果每次回收有多于10%的对象存活,那么一块Surviver就不够用了,此时需要依赖于老年代进行空间分配担保,也就是借用老年代的空间存储放不下的对象。
4、分代收集
现在的商业虚拟机采用分代收集算法,他根据对象存活周期将内存划分为几块,不同的块采用适当的收集算法。
一般将堆分为新生代和老年代。
-
新生代使用:复制算法(对象少,垃圾多)
-
老年代使用:标记-清楚或者标记-整理算法。(对象多,垃圾少)
内存分配于回收策略
Minor GC和Full GC
-
Minor GC:回收新生代,因为新生代对象存活时间很短,因此Minor GC会频繁执行,执行速度一般也会比较快。
-
Full GC:回收新生代和老年代,老年代对象其存活时间长,因此Full GC很少执行,执行对象回比Minor GC慢很多。
内存分配策略
1、对象优先在Eden分配
大对数情况下,对象在新生代Eden上分配,当Eden空间不够时,发起Minor GC。
2、大对象直接进入老年代
大对象是指需要连续分配内存空间的对象,最典型的大对象是那种很长的字符串以及数组。
经常出现大对象会提前触发垃圾收集以及获取足够的连续空间分配给大对象。
-XX:PretenureSizeThreshold,大于此值的对象直接在老年代分配,避免在 Eden 和 Survivor 之间的大量内存复制。
3、长期存活的对象进入老年代
为对象定义年龄计数器,对象在Eden出生并经过Minor GC依然存活,将移动到Surviver中,年龄就增加1岁,增加到一定年龄则移动到老年代中。
-XX:MaxTenuringThreshold 用来定义年龄的阈值。
4、动态对象年龄判断
虚拟机并不是永远要求对象的年龄必须达到MaxTenuringThreshold才能晋升老年代,如果在Surviver中相同年龄所有对象大小的总和大于Surviver空间的一半,则年龄大于或等于该年龄的对象可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄。
5、空间分配担保
在发生Minor GC之前,虚拟机先检查老年代最大可用的连续空间是否大于新生代所有对象空间,如果条件成立的话,那么Minor GC可以确认是安全的。
如果不成立的话,虚拟机会查看 HandlePromotionFailure 的值是否允许担保失败,如果允许那么就会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次 Minor GC;如果小于,或者 HandlePromotionFailure 的值不允许冒险,那么就要进行一次 Full GC。
Full GC的触发条件
对于Minor GC,其触发条件非常简单,当Eden空间满时,就触发一次Minor GC。而Full GC则相对复杂,有以下条件:
1、调用System.gc()
只是建议虚拟机执行Full GC ,但是虚拟机不一定真正去执行。不建议使用这种方式,而是让虚拟机管理内存。
2、老年代空间不足
老年代空间不足的常见场景为前文所讲的大对象直接进入老年代,长期存活对象进入老年代等。
为了避免以上原因引起的Full GC,应当尽量不要创建过大的对象以及数组。除此之外,可以通过-Xmn虚拟机参数调整大新生代的大小,让对象尽量在新生代被回收掉,不进入老年代。还可以通过-XX:MaxTenuringThreshold调大对象进入老年代的年龄,让对象在新生代多存活一段时间。
3、空间分配担保不足
使用复制算法的Minor GC需要老年代的内存空间作担保,如果担保失败会执行一次Full GC。
4、JDK1.7及以前的永久代空间不足
在 JDK 1.7 及以前,HotSpot 虚拟机中的方法区是用永久代实现的,永久代中存放的为一些 Class 的信息、常量、静态变量等数据。
当系统中要加载的类、反射的类和调用的方法较多时,永久代可能会被占满,在未配置为采用 CMS GC 的情况下也会执行 Full GC。如果经过 Full GC 仍然回收不了,那么虚拟机会抛出 java.lang.OutOfMemoryError。
为避免以上原因引起的 Full GC,可采用的方法为增大永久代空间或转为使用 CMS GC。
5、Concurrent Mode Failure
执行CMS GC的过程中同时有对象要放入老年代,而此时老年代空间不足(可能是GC过程中浮动垃圾过多导致暂时性的空间不足),便会报Concurrent Mode Failure错误,并触发Full GC