JVM垃圾回收机制
文章目录
一、什么是垃圾回收机制
垃圾回收是一种自动的存储管理机制。当一些被占用的内存不再需要时,就应该予以释放,以让出空间,这种存储资源管理,称为垃圾回收。
JVM内存
JVM将内存划分为五个区间。
JVM分为五大内存空间,程序计数器、虚拟机栈、本地方法栈3个区域随线程而生、随线程而灭,因此这几个区域的内存分配和回收都具备确定性,就不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟随着回收了。而Java堆区和方法区则不一样,这部分内存的分配和回收是动态的,正是垃圾收集器所需关注的部分。
年轻代分为Eden区和survivor区(两块儿:from和to),且Eden:from:to==8:1:1。
jvm内存结构
1)新产生的对象优先分配在Eden区(除非配置了-XX:PretenureSizeThreshold,大于该值的对象会直接进入年老代);
2)当Eden区满了或放不下了,这时候其中存活的对象会复制到from区。
这里,需要注意的是,如果存活下来的对象from区都放不下,则这些存活下来的对象全部进入年老代。之后Eden区的内存全部回收掉。
3)之后产生的对象继续分配在Eden区,当Eden区又满了或放不下了,这时候将会把Eden区和from区存活下来的对象复制到to区(同理,如果存活下来的对象to区都放不下,则这些存活下来的对象全部进入年老代),之后回收掉Eden区和from区的所有内存。
4)如上这样,会有很多对象会被复制很多次(每复制一次,对象的年龄就+1),默认情况下,当对象被复制了15次(这个次数可以通过:-XX:MaxTenuringThreshold来配置),就会进入年老代了。
5)当年老代满了或者存放不下将要进入年老代的存活对象的时候,就会发生一次Full GC(这个是我们最需要减少的,因为耗时很严重)。
二、堆区的垃圾回收
什么是垃圾
如果应该对象已经没有任何应该地方引用他,他就是垃圾
怎么确认是垃圾
引用计数法
给对象添加一个引用计数器,每当有一个地方引用它时,计数器加一。反之每当一个引用失效时,计数器减一。当计数器为0时,则表示对象不被引用。
可达分析
设立若干根对象(GC Root),每个对象都是一个子节点,当一个对象找不到根时,就认为该对象不可达。
触发条件
Minor GC触发机制
当年轻代满时就会触发Minor GC,这里的年轻代指的是Eden代满,Survivor满不会引发GC。
FULL GC触发机制
老年代空间不足方法区空间不足通过Minor GC后进入老年代的平均大小大于老年代的可用内存。由Eden区、From Survivor区向To Survivor区复制时,对象大小大于To Space可用内存,则把对象转存到老年代,且老年代的可用内存小于该对象大小System.gc()方法的调用,系统建议执行Full GC,但是不必然执行。
三、垃圾回收算法
标记清除
标记清楚算法将垃圾回收分为俩个阶段:标记阶段和清除阶段。
在标记阶段首先通过根节点(GC Roots),标记所有从根节点开始的对象,未被标记的对象就是未被引用的垃圾对象。然后,在清除阶段,清除所有未被标记的对象。
- 适用场合:
- 存活对象较多的情况下比较高效
- 适用于年老代(即旧生代)
- 缺点:
- 容易产生内存碎片,再来一个比较大的对象时(典型情况:该对象的大小大于空闲表中的每一块大小但是小于其中俩块的和),会提前触发垃圾回收机制
- 扫描了整个空间俩次(第一次:标记存活对象;第二次:清除没有标记的对象)
复制算法
从根集合节点进行扫描,标记出所有的存活对象,并将这些存活的对象复制到一块新的内存,之后将原来的那一块儿内存全部回收掉;
现在的商业虚拟机都采用这种收集算法来回收新生代。
适用场合:
- 存活对象较少的情况下比较高效
- 扫描了整个空间一次(标记存活对象并复制移动)
- 适用于年轻代(即新生代):基本上98%的对象是“朝生夕死”的,存活下来的会很少
缺点:
- 需要一块儿空的内存空间
- 需要复制移动
标记整理
复制算法的高效性是建立在存活对象少、垃圾对象多的前提下。
这种情况在新时代经常发生,但是在老年代更常见的情况是大部分对象都是存活对象。如果依然使用复制算法,由于存活的对象较多,复制的成本也将很高。
标记-压缩算法是一种老年代的回收算法,它在标记-清除算法的基础上做了一些优化。
首先也需要从根节点开始对所有可达对象做一次标记,但之后,它并不是简单的清除未标记的对象,而是将所有的存活对象压缩到内存的一端。之后,清理边界外所有的空间。这种方法既避免了碎片的产生,又不需要俩块相同的内存空间,因此,其性价比比较高。
分代收集算法
分代收集算法就是目前虚拟机使用的回收算法,它解决了标记整理不适用于老年代的问题,将内存分为各个年代。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),在堆区之外还有一个代就是永久代(Permanet Generation)。
在不同年代使用不同的算法,从而使用最适合的算法,新生代存活率低可以使用复制算法。而老年代对象存活率高,没有额外空间对它进行分配担保,所以只能使用标记清除或者标记整理算法。