1、对象已死?
a、引用计数算法:缺点是它很难解决对象之间的相互循环引用的问题,Java语言中没有选用它。
b、根搜索算法(GC Roots Tracing):通过一系列的名为“GC Roots”的对象作为起始点,开始向下搜索,走过的路径称为引用链,当一个对象没有任何引用链相连,表面此对象不可达。在Java语言中,可作为GC Roots的对象包括:
虚拟机栈(栈帧中的本地变量表)中的引用的对象。
方法区中的类静态属性引用的对象。
方法区中的常量引用的对象。
本地方法栈中JNI的引用的对象。
c、再谈引用:Java把引用分成强引用,软引用,弱引用和虚引用。由强到弱。
强引用:只要强应用在存在,垃圾收集器永远不会回收掉被引用的对象。
软引用:在系统将要发生内存溢出异常之前,把对象列进回收范围之中并进行第二次回收。如果这次回收之后还是没有足够的内存,才会抛出内存溢出异常。SoftReference
弱引用:被弱引用关联的对象只能生存到下一次垃圾回收发生之前。WeakReference
虚引用:一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。唯一目的是:对象被回收时收到一个系统通知。PhantomReference
d、生存还是死亡?
对象不可达之后,进入“缓刑”阶段。第一次根搜索后,不可达对象有两种情况:一是直接回收,二是放入F-Queue队列。放入F-Queue队列的条件是覆盖了finalize()方法且没有被调用过。总结一下就是:对象可以通过覆盖finalize()方法在被GC时拯救自己一次。注意:任何一个对象的finalize()方法都只会被系统自动调用一次。
e、回收方法区
方法区的回收主要集中在:废弃常量和无用的类。废弃常量的回收与Java堆对象的回收类似,无用类的回收需要满足3个条件:而且只是可以回收,通过参数控制。
该类的所有实例都已经被回收
加载该类的ClassLoader已经被回收
该类对应的Class对象没有在任何地方被引用。
在大量使用反射、动态代理、CGLib等bytecode框架的场景,以及动态生成JSP和OSGi这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能。
2、垃圾收集算法
a、标记-清除算法:分成标记和清除两个阶段。首先标记出所有需要回收的对象,标记完成后统一回收掉所有被标记的对象。
有点:实现简单,逻辑直接
缺点两个:效率比较低,空间不连续,大量碎片。
b、复制算法:为了解决效率问题。平分内存成两块,每次只用一块,快用完时,复制替换。
优点:实现简单,运行高效
缺点两个:可用内存是原来的一半;当对象存活率较高时,复制带来低效率。
HotSpot虚拟机吻合了IBM的研究,改进了该算法,分成了一个较大的Eden区和两个较小的Survivor区。效率更好,但需要担保内存。比较符合新生代使用。
c、标记-整理算法:不直接清理可回收对象,让所有存活的对象向一端移动,然后清理掉边界以外的内存。比较适合于老年代使用。
d、分代收集算法:根据对象的存活周期不同将内存划分为几块。根据各个年代的特点采用最合适的收集算法。新生代一般使用复制算法,老年代采用标记-清除算法或者标记-整理算法。
3、垃圾收集器
上图展示了7种作用于不同分代的收集器,连线表示收集器之间可以搭配使用。
a、Serial收集器:
它是一个单线程的收集器,只会使用一个CPU和一条收集线程去完成垃圾收集工作。同时,它也是串行收集器:在进行垃圾收集时,必须暂停其他所有的工作线程(Stop The World),直到它收集结束。在用户不可见的情况下,把用户的正常工作线程全部停掉。
这个简单而高效(特别是单个CPU的环境)的收集器适合于运行在Client模式下的虚拟机。
b、ParNew收集器
是Serial收集器的多线程版本,与Serial收集器唯一的区别就在于使用了多条线程进行收集。
目前只有Serial和ParNew能与CMS收集器配合工作。CMS是JDK1.5中引入的第一款并发收集器:垃圾收集线程与用户线程可同时工作。ParNew在多CPU的情况下才具备性能优势。
c、Parallel Scavenge收集器
是一个新生代收集器,使用复制算法,并行多线程收集器。它的特点是关注于达到一个可控制的吞吐量,最大地利用CUP的时间。同时,Parallel Scavenge收集器通过一个开关参数还可以开启自适应调节策略。
d、Serial Old收集器
它是Serial收集器的老年代版本,单线程,标记-整理算法。主要用于Client模式下。同时:可以Parallel Scavenge收集器搭配使用,作为CMS收集器的后背预案。
e、Parallel Old收集器
它是Parallel Scavenge收集器的老年代版本,多线程和标记-整理算法。与Paralle Scavenge一起打造吞吐量优先的组合。在注重吞吐量及CPU资源敏感的场合,考虑使用。
f、CMS收集器(Concurrent Mark Sweep)
以获取最短回收停顿时间为目标,适合于注重服务的相应速度的场合。基于标记-清除算法。整个过程四个步骤:
初始标记(CMS initial mark)
并发标记(CMS concurrent mark)
重新标记(CMS remark)
并发清除(CMS concurrent sweep)
注意:初始标记和重新标记一样有Stop The World。初始标记只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段就是进行Roots Tracing的过程,而重新标记则是为了修正并发标记期间,因用户程序运作而导致标记发生变动的那一部分对象的标记记录,停顿时间不长。耗时较长的并发标记和并发清除过程,收集线程和用户线程可一起工作。
缺点:
对CPU资源非常敏感。默认启动线程数:(CPU数量+3)/4。
无法处理浮动垃圾,可能出现Concurrent Mode Failure失败导致一次Full GC。CMS运行期间预留的内存无法满足程序的需要,出现Concurrent Mode Failure失败。虚拟机启动后备预案,临时使用Serial Old收集器重新老年代的垃圾收集。
使用的标记-清除算法导致大量内存碎片。可能导致提前Full GC。使用参数可控制Full GC发生后附带的空间碎片整理频率。
g、G1收集器
与CMS相比有两个改进:基于标记-整理算法,消除了内存碎片问题;精确地控制停顿时长。G1将整个Java堆分成多个大小固定的独立区域,跟踪区域垃圾堆积程度,在后台维护一个优先列表,每次根据允许的收集时间,优先收集垃圾最多的区域。
4、垃圾收集器参数总结