GC机制
-GC机制是Java的一种垃圾回收机制,在内存不足时会触发该机制回收内存。
在聊到GC机制的时候会着重讨论到3个问题,围绕这个3个问题便可以好好的理清楚GC机制。
第一个问题:
什么对象可以回收?
关于什么对象可以回收的问题,要解决的便是什么对象已经被JVM认为失去了作用,可以被回收,这里设计到两种方法:
1、引用计数法
引用计数法指的是在一个对象身上布置一个计数器,当对象被另一个对象引用时计数器+1,当对象引用消失时-1,实现比较简单。当引用计数器为0时,则判断该对象可以回收。
缺点:当两个对象出现循环引用,即你中有我,我中有你时便会出现二者计数器都不为0,永远不会被回收。
2、GCRoot
GCRoot的思路是定义一部分的对象为根节点,若一个对象跟这个根节点之间产生间接的引用关系,说明这个对象是有用的,不进行回收,否则进行回收。
GCRoot的定义节点一般是重要的对象,回收后影响可以能比较大的对象,主要有:
1、被启动类加载器加载的类和示例
2、JNI引用的对象
3、局部变量对象
4、方法区中的引用和常量引用。
以上两种方法便可以回答什么对象可以回收的问题。
接下来是第2个问题:
什么时候进行回收?
在需要进行发生GC的时候并不会直接随时进行GC。GC的时机如何进行考虑呢?
很明显,对对象进行回收的时候会对对象的引用关系进行一个改变。同时还有一个概念,在JVM当中会存有一个OOPMAP来存储所有的对象引用,但是这个OOPMAP需要占用一定的内存,并不是任何节点都会持有。
因此进行GC的前提可以说就是要先拥有OOPMAP,这里就可以引出GC执行的时机:
1、安全点:
安全点指的是Java程序在执行到某一点的时候,该点上拥有者OOPMAP。在安全点上进行OOPMAP能够才能够比较合理的进行引用修改。
因此程序在执行的时候只有达到安全点才会GC,否则会等待安全点的到来。
安全点一般有:方法调用、循环跳转、异常跳转等。
2、安全区域:
当然可能出现安全点很长一段时间都不会到达的情况,因此便出现了一个安全区域的概念,安全区域指的是某段代码段,在该代码段上引用不会发生改变,因此在该区域进行GC是安全的。
有了什么时候以及什么对象可以回收之后,接下来出现的问题就是如何进行回收的问题:
对象如何进行回收?
对象如何进行回收的话主要设计两方面的问题,垃圾回收算法和垃圾回收器。
垃圾回收算法:
1、标记-清除算法(MS)
标记清除算法指的是先标记所有需要进行GC的对象,然后再进行清除操作。
标记清除算法是垃圾回收算法的最早版本。但是其缺点是会造成很多的碎片。
2、标记-复制算法:
这种算法把内存分为两个部分,对其中一部分的对象进行标记,然后再将存活的部分复制到另外一边。这种方法不会产生碎片的现象,但是会浪费一半的空间。
3、标记整理算法:
这种算法可以理解为在执行了标记-清除算法之后,再将存活的对象移动到一边,避免了内存碎片的情况。其代价要小于标记复制算法,但是效率较低。
因此在垃圾回收器算法选择方面,一般对于新生代会选择标记-复制算法,对于老年代一般会选择标记-整理算法。
垃圾回收器:
垃圾回收器的分类可以站在使用新生代和老年代的情况来看:
1、新生代(复制算法):
Serial: 单线程、简单高效、stop the world
ParNew:Serial的多线程版本。是唯一一个能够和CMS进行合作使用的版本
Parallel Scavenge :侧重吞吐量的一个垃圾回收器,使用多线程并行收集垃圾的收集器。
其最大的特点就是为了保持最大吞吐量,做了一个GC自适应调节策略。在执行的过程中会根据情况调整GC时机,达到一个较好的吞吐量水平。
2、老年代(整理算法):
SerialOld:Serial的老年版本
Parallel Old:ParNew的老年待版本
CMS收集器(标记清除算法):标记清除算法、并发收集、低响应。侧重于用户响应时间的垃圾收集器,尽量基于用户最低的响应体验。在收集时经历几个步骤:
1、初始标记:标记GCRoot能够直接引用到的对象。
2、并发标记:并发标记所有间接引用到的对象
3、重新标记:修正由于并发标记期间发生变化的对象。需要STOP THE WORLD
4、并发清除:并发清除所有的未标记对象。
优点:响应快,
缺点:
1、标记清除算法会导致碎片问题。
2、对CPU资源敏感。
3、会产生浮动垃圾
G1收集器:
一款面向服务器的垃圾收集器。
特点:
1、并行与并发:通过多线程、多CPU调度来维持一个比较高的效率。
2、分代收集:放弃了传统的新生代老年代界限,使用了region来做区域划分,每个region分别存放新、老年代,并且依据回收的代价进行排序。最终根据用户提供的预计响应时间参数来进行选择回收。
3、空间整合:通过局部的标记-复制算法和整体的标记-移动算法来保持空间完整。
4、可预测的停顿:通过排序回收代价来保持一个可预测的停顿。
回收过程:
1、初始标记:仅标记GC Roots能直接到的对象,并且修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象。(需要线程停顿,但耗时很短。)
2、并发标记:从GC Roots开始对堆中对象进行可达性分析,找出存活对象。(耗时较长,但可与用户程序并发执行)
3、最终标记:为了修正在并发标记期间因用户程序执行而导致标记产生变化的那一部分标记记录。且对象的变化记录在线程Remembered Set Logs里面,把Remembered Set Logs里面的数据合并到Remembered Set中。(需要线程停顿,但可并行执行。)
4、筛选回收:对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划。(可并发执行) -