GC是什么?
GC英文全称为grabage collection,因为java是运行在jvm上的,而jvm分配对象时再堆上分配,当jvm想要分配对象而堆空间不够时就会触发GC。
哪些是GC对象。
GC的对象时那些不在被需要的对象,可以称其为死亡对象。判断一个对象是否死亡有两种方法。一种是引用计数法,,每个对象有一个引用计数器,当它被应用就把计数器加一,当取消对它的应用就把计数器减一,当gc是对象的引用计数器值为0时就被判定死亡。这种方法的缺点是无法回收相互应用的对象,比如对象a持有对象b的应用,对象b也持有对象a的应用,而jvm中没有其他对象对这两个对象的引用,这两个对象本该被回收,但因引用计数器不为0而无法被回收。
还有一种是可达性分析算法。从一些被称为GC ROOT的对象出发,根据它所持有的引用向下搜索,搜索得到的对象就是存活的对象,而遍历所有GC ROOT都无法搜索的到的对象就是死亡对象。GC ROOT对象有以下这些。
- 类常量或者类静态属性引用的对象。
- 栈中(局部变量表)引用的对象。
- JNI(本地方法栈)中引用的对象。
清理的对象的算法有哪些?
清理算法有3种。分别是
标记-清理算法
先标记那些死去的对象,然后回收它们占有的内存。标记清理算法的特点是简单高效,但是缺点也很明显,会产生内存碎片,可能会导致分配一个对象虽然剩下的内存总和足够但是没有单个的内存碎片满足要求,就会触发一次GC。
复制算法
复制算法把内存分为两部分,平时只使用一部分,当GC时把存活的对象移到另一部分,然后清理原来那部分空间,如此循环往复。复制算法解决了内存碎片的问题,因为把存活对象复制到另一部分时会把对象整齐的放在一起。但是复制算法只用到了内存的一部分,而存活对象较多时复制算法的效率也比较低,甚至会内存不够容纳存活对象的情况,这种问题在后面讨论。
标记-整理算法
标记整理算法前面的步骤和标记-清理算法一样,标记死亡对象,但是后面是把存活对象移动到内存的一端,然后清理掉内存其他部分。标记整理算法似乎兼并了标记-清理算法和复制算法的优点,既不用专门分配一部分空间给存活对象,也不会产生内存碎片,但是它的性能还是比复制算法差的。(来自https://www.cnblogs.com/zuoxiaolong/p/jvm5.html,4楼的评论)
似乎每种算法都有一定的缺点和优点,jvm使用的是哪种算法呢?
jvm使用的一种分代算法,即把内存分为新生代和老年代部分,根据研究,刚创造的对象大部分会在下一次GC中被回收,而一些存活了很久的对象则不太可能会在下一次GC中被回收。所以内存被分为新生代和老年代,新生代的对象"朝生夕死",所以很适合用复制算法,新生代80%的区域为Eden,20%被分为两块Survivor,10%为From,10%为To。Jvm运行时只使用Eden区和from区,垃圾回收时将存活对象复制到to区并清理其他区域。然后to区和from区对调,然后接下来继续使用from区域和eden区。老年代对象又"老而不死",使用标记-整理算法比较合适。而针对新生代的GC被称为Mirror GC,针对堆中全部对象的GC被称为Full GC,很明显MIrror GC比Ful.GC频繁,而且Full GC比Mirror GC慢10倍以上,所以应该尽量避免Full GC的频繁发生。
Mirror GC发生的情况就是当要分配内存给对象时空间不够,此时就会发送一个Mirror GC。但并不是所有对象一开始都被分配在新生代,jvm中有个参数-XX:PertenureSizeThreshold(以bit为单位),设定了一个值,大小大于这个值的对象会被直接分配到老年代,这个参数只对Serial和ParNew这两款回收器有用。
新生代晋升到老年代的一个比较典型的方式就是经过一定次数的GC后仍然存活,每个对象有一个年龄计数器,经过一个MirrorGC后就加一,jvm中用-XX:MaxTenuringThreshold这个参数限定一个对象到多少岁后可以进去老年代。
动态对象判定年龄
新生代对象晋升到老年代还有另一种方式,但GC后存活的同年龄对象总和 大于Survivor一半的空间(也就是新生代5%的空间)时,这些对象也会晋升到老年代。
空间担保分配
每次进行Mirror GC前jvm会检测老年代的最大连续可用空间是否能容纳新生代中的所有对象,如果可以,说明这次Mirror GC是安全的,即使To区域无法容纳存活下来的对象,也可以把它们移到老年代。若老年代空间不够容纳存活对象,jvm会检测历次晋升到老年代对象的平均大小,如果小于老年代剩余空间,那jvm会检测是否允许冒险(由参数HandleePromotionFailure决定,JDK 6 Update 24后不再由该参数决定,jvm一直认为允许冒险)再进行一次Mirror GC。如果不允许冒险或者担保失败,那么jvm还是会进行一次Full GC,若Full GC后空间还是不够,则jvm会抛出outofmemorty异常。
本文主要参考<<深入理解Java虚拟机,JVM高级特性与最佳实践>>