在java中,GC的主要对象是堆空间和永久区。而我们更多讨论的是堆空间这部分。
堆的内存分配图
GC的工作目的很明确:新生成的对象,都放在Eden中;当Eden充满时(小孩太多 了),GC将开始工作,首先停止应用程序的运行,开始收集垃圾。在堆中,找到已经无用的对象,并把这些对象占用的空间收回使其可以重新利用.大多数垃圾回收的 算法思路都是一致的:把所有对象组成一个集合,或可以理解为树状结构,从树根开始找,只要可以找到的都是活动对象,如果找不到,这个对象就是凋零的昨日黄 花,应该被回收了。
GC算法:引用计数法,标记清除,标记压缩,复制算法,主要是这四种。
一.引用计数法的原理:对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1,当引用失效时,引用计数器就减1。只要对象A的引用计数器的值为0,则对象A就不可能再被使用。
缺陷:引用和去引用伴随加法和减法,影响性能很难处理循环引用。所以java中并没有使用引用计数法。
二.标记-清除算法是现代垃圾回收算法的思想基础。标记-清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。一种可行的实现是,在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象,可达对象可理解为被引用的对象,因此,未被标记的对象就是未被引用的垃圾对象。然后,在清除阶段,清除所有未被标记的对象。如图中,标记阶段先标记,清除阶段,清除未标记为引用的对象。
三.标记-压缩算法适合用于存活对象较多的场合,如老年代一般使用这种算法,对老年代垃圾回收又叫MajorGC。它在标记-清除算法的基础上做了一些优化。和标记-清除算法基本思想一样,标记-压缩算法也首先需要从根节点开始,对所有可达对象做一次标记。但之后,它并不简单的清理未标记的对象,而是将所有的存活对象压缩到内存的一端,就是移到一边。之后,清理边界外所有的空间。如图2
图2
四。复制算法:与标记-清除算法相比,复制算法是一种相对高效的回收方法,不适用于存活对象较多的场合 如老年代。复制算法一般使用在新生代,对新生代进行垃圾回收又叫做MinorGC(他和老年代不冲突)。将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收
图3
如上图3,复制算法执行后,先把存活对象复制到另一块区域,然后对之前区域进行一次清理,第一张图所画的A,B空间,把存活的对象由A复制到B,然后清理A区域,再把B区域存活对象复制到A,来回复制,每被复制一次,就会加一计数,当到达十五次(,默认应该是十五次,可以设置)之后,就进入了老年代,如下图4,还有一部分对象就是大对象,因为占得空间比较大,复制比较占空间,会直接进入老年代。
所以:少量对象存活,适合复制算法。大量对象存活,适合标记清理或者标记压缩。
补充名词:
1.top-The-World:多半由于GC引起的,Java中一种全局暂停的现象,全局停顿,所有Java代码停止。native代码可以执行(但不能和JVM交互)。Jvm处于挂机状态,当然Dump,线程死锁检查,堆Dump,也是有可能引起全局暂停的现象,但多数还是GC引起。
2.可触及的对象:从根节点可以触及到这个对象。
3.不可触及的对象:在finalize()调用后,可能会进入不可触及状态,不可触及的对象不可能复活,可以回收。
4.可复活的对象:一旦所有引用被释放,就是可复活状态,因为在finalize()中可能复活该对象。
GC时为什么会有全局停顿?
类比在聚会时打扫房间,聚会时很乱,又有新的垃圾产生,房间永远打扫不干净,只有让大家停止活动了,才能将房间打扫干净。
注意事项:
1.当eden满了,触发young GC;
2.young GC做2件事:一,去掉一部分没用的object;二,把老的还被引用的object发到survior里面,等下几次GC以后,survivor再放到old里面。
3.当老年代满了,触发full GC。full GC很消耗内存,把old,young里面大部分垃圾回收掉。这个时候用户线程都会被block。
4.young generation比例越大,不一定最好。将young的大小设置为大于总堆大小的一半时会造成效率低下。如果设置得过小,又会因为young generation收集程序不得不频繁运行而造成瓶颈。
5.在sun 的文档说明中,对JVM堆的新域,是采用coping算法,该算法的提出是为了克服句柄的开销和解决堆碎片的垃圾回收。