对于java的垃圾回收机制,其实无非就是两个问题:
1.对象是否是可回收对象;
2.如何对对象进行回收;
一.判断对象是否是可回收对象:
1.1.引用计数算法
引用计数算法的思想就是,给一个对象添加计数器,当一个对象被引用时,计数器值加1,在被引用又加1;当引用失效的时候计数器减1;
弊端:这个算法实现先对其他判断对象是否是可回收对象的算法来说是很简单的,但是他也存在一个很大的问题,就是当两个无效对象互相引用的时候,它们各自的计数器永远是1,这种情况下gc收集器就无法收集它了;
1.2.可达性分析算法
可达性分析算法的思想就是,以"GC Roots"的对象作为起始点,然后向下遍历,如果不包含在该引用链的对象就将它gc掉;
不过可达性分析算法有个特点,不一定不再引用链上的对象非死不可,下面我解释一下:
在object对象里面有一个方法finalize():
上面一大串英语对finalize()的描述:当引用链中没有该对象的时候,就执行一下该对象的finalize()方法,如果在这里里面有引用,那就保留它,不gc;单是第二次在gc判断的时候,就不会再执行finalize()方法了,如果该对象没有引用就直接gc掉了;
二.回收方法区
方法区的垃圾回收和堆的回收有点不同,它包括两种类型的回收:废弃常量和无用的类;
废弃常量使用可达性分析算法,判断常量池的它是否被引用,没有被引用那就gc掉;
而无用的类的判断稍微麻烦一点,判断条件:
1.该类没有任何实例;
2.加载该类的ClassLoader已经被回收;
3.该类对应的java.lang.Class对象没有在任何地方被引用;
三.垃圾回收算法
3.1标记--清除算法
该算法是最基础的算法,因为后面的理解回收算法都是基于它进行的改进,核心思想就是:标记出所有需要回收的对象,在标记完成后,统一清除所有标记对象(如何标记,就是判断它是否可回收).
弊端:
1.效率不高,标记和清除两个过程效果不高;
2.空间碎片化,由于清除之后产生了大量不连续的内存碎片;相当于将内存碎片化,当要分配较大对象的时候,就不得不又进行一次gc操作来或者能够装下该对象的内存空间;
3.2复制算法
为了解决标记--清除算法的效率和空间问题,出现了复制算法,它的思想:将内存区域划分成两块一样的区域,每次只使用一块区域,并且标记那个需要gc的对象,当该区域买了之后,将该区域存活的对象复制到另外一个区域,,然后一次性清理之前已使用过的那块内存区域.,这样的话就可以提高之前标记--清除算法的效率,而且也解决了空间碎片化的问题.
弊端:始终有一块需要是空下来的;
使用场景:在如今java虚拟机中,采用的就是该算法,在堆中,将内存分为一块较大的Eden区域和一块较小的Survivor区域(我是按照jdk8的内存来讲的,jdk7之前的已经抛弃了),对象先加载到Eden区域,当Eden区域进行gc标记并且满了之后,将未被标记的对象放到Survivor区域中.然后一次性清理Eden区域.
3.3标记--整理算法
复制收集算法看起来已经很完美了,但是有没有考虑到,如果在某个区域中的对象基本上很少有gc的话,你每次都进行复制,然后移动,这样的话空间浪费就很大了,效率也很低.
而这种场景就是老年代区域;因为老年代区域的话基本上很少有要gc的对象,所以根据这个场景就出现了标记--整理算法,其标记方式和最开始的标记--清除算法一样都是采用可达性分析算法来判断的,但是不同之处在于标记之后,将未标记的的对象放到一端,然后一次性清除另外一端所以的数据.
3.4分代收集算法
因为复制算法和标记--整理算法各种都有优点,所以为了能够发挥出他们各自的优点,就出现了分代收集算法;
算法思想:将java堆内存分为新生代和年老代,然后根据各个年代的特点选择适合的回收算法.在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量的存活,那就选择复制算法;而年老代中因为对象存活率高,没有额外空间对它进行分配担保,就必须使用"标记--整理"算法来进行回收.
四.垃圾收集器
如果说收集算法是内存回收的方法论,那么垃圾收集器就是垃圾回收的具体实现.
4.1Serial收集器
这个收集器是一个单线程的收集器,也就意味着当serial收集器进行垃圾回收的时候,整个程序都必须停止下来,知道等到它收集完毕为止,这个停止的过程称为"Stop The World".其实这个很好理解,假设在eden区域中(采用复制算法)执行了gc标记,满了之后准备转移的时候,又突然加入一个可能要被gc的对象,而这个对象还没来及被标记,就被转移到Survivor区域里面了,当这样的对象数量很大的情况下,这不就导致Survivor区域内存空间浪费了吗?
所以"Stop The World"这个现象的出现是能够理解的;
4.2ParNew收集器
ParNew收集器其实就是Serial收集器的多线程版本.而且它是出了Serial收集器之外,唯一一个能够与CMS(Concurrent Mark Sweep)收集器配合使用的收集器.
4.3Parallel Scavenge收集器
Parallel Scavenge收集器(并行)和其他收集器的不同点在于,它的关注点在于吞吐量,笔者感觉这个基本上快被淘汰了,愿意在于和CMS不能配合使用;所以不详细讲解了;
4.4Serial Old收集器
Serial Old收集器是Serial的年老代版本,同样是单线程,使用"标记--整理"算法;其作用;
1.和Parallel Scavenge匹配使用;
2.是CMS收集器的预备方案;
4.5Parallel Old收集器
多线程(并行)和"标记--整理"算法,基本上已经不用;
4.6CMS收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器.
相对其他收集器来说,复杂一些,它分为四个步骤:
初始标记:仅仅标记一下GC Roots能直接关联到的对象;
并发标记:进行GC Roots Tracing的过程;
重新标记:为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录;
并发清除:清除标记的对象
4.7G1收集器
可以取代CMS收集器的G1收集器
运作的大致步骤:
初始标记;
并发标记;
最终标记;
筛选标记;