前言:

说起垃圾收集器,JAVA开发者肯定是听得耳朵都起茧子了。如果让你设计一个JAVA垃圾收集器,那么你关注那些点呢?

// 1.哪些内存需要回收?
// 2.什么时候回收?
// 3.如何回收?

这篇博文就是记录这些问题答案的。闲言碎语不多讲,开始写。


那些内存需要回收?

我们先来回顾一下“运行时数据区”的知识点。我们都知道程序计数器虚拟机栈本地方法栈都是与线程同生共死的。栈中的栈帧分配多少内存在类结构确定下来时就已经确定了。所以它们的内存分配和回收都是非常确定性的,因为方法结束或者线程结束内存自然就回收了。
但是Java堆方法区就不一样了,一个接口中的多个实现类需要的内存可能不一样,一个方法的多个分支需要的内存也可能不一样,只有在程序执行期间才能知道会创建哪些对象。这部分内存的分配和回收都是动态的,垃圾收集器关注的就是这部分内存。

什么时候回收?

java堆中几乎存着所有的java世界中的对象实例,垃圾收集器对堆进行回收前,首先要做的事情就是:确定不可能再被任何途径使用的对象。这件事当然是交给算法去做,引用计数算法根搜索算法

  1. 引用计数算法

给对象添加一个引用计数器,每当有一个地方引用它时,计数器加1,当引用失效时,计数器值减1.如果任何时候计数器都为0的对象就是不可能再被使用的。书中说实际上java虚拟机没有使用这种算法去管理内存,因为它很难解决对象间相互循环引用的问题。比如说:A对象,B对象,他们相互引用,那么他们的引用计数器就不会为0,那么GC就永远没法回收它们,实际上他们已经可以被回收了。这个案例也可以java虚拟机到底是不是使用的引用计数器算法。

2.根搜索算法
算法基本思路是:通过一系列的名为“GC Roots”的对象为起点,从这些节点开始向下搜素,搜素所有走过的路径称为引用链。当一个对象到GC Roots没有任何链路时,则说明该对象是不可用的。

3.引用
如果reference类型的数据中存放的数值是代表另一块内存的起始地址,我们就说这块内存代表着一个引用。引用分类如下:
强引用:(如Object obj = new Object())、永远不会被垃圾收集器掉引用的对象。

软引用:描述还有用,但非必须的对象。在系统面临内存溢出风险前,将把这些加入到回收范围进行二次回收。

弱引用:描述非必需的对象,只能生存到下一次垃圾收集发生之前。

虚引用:幽灵引用,不会对生存时间构成影响,也无法通过虚引用获取对象实例。唯一目的就是在回收时,能够收到一个系统通知。

如何回收

垃圾收集算法
上面讲了通过算法判断是否将对象列入回收名单中,那么接下来就是讲解垃圾收集算法。介绍几种算法的思路和发展过程,具体实现不在此详细说明。
标记-清除算法
分两个阶段:标记、清除。将所需要回收的对象进行标记,在标记完成后,统一回收掉所有被标记的对象。所谓标记也就是前面说说的内容,如何判断一个对象已经“死了”。这个算法效率不高,因为两个阶段效率都不高,而且在回收完成后,会产生大量不连续的内存碎片,当需要给大对象进行内存分配时不得不触发另一次垃圾回收动作。

复制算法
为了解决效率问题,一种称为复制的收集算法出现了,它将可用内存按照容量划分为两个大小相等的区域,每次只使用其中一块,当一块用完以后,就将还存活的对象复制到另外一块上,然后一次性清理掉当前满了的区块。

标记-整理算法
复制算法当对象的存活率很高时,需要大量的复制对象,从而降低效率,而且该种方式降低了可用内存大小。标记-清理算法和标记-整理算法前面阶段是相同的,在标记完成以后,不做清理,而是将存活对象,往一边移动,使得会搜狐的内存能够连续在一起。

备注:
没有完美的算法,只有适用场景的算法。上述的算法都有各自的优缺点,只用一种算法都无法很好的覆盖虚拟机中内存回收的场景。所以出现了分代回收算法。其实这个也没有什么新意,就是把对象根据存活周期划分不同的内存区域,从而针对不同的内存区域进行不同算法的垃圾收集。

分代回收算法
一般java堆中分为新生代和老年代。根据各个分代的特点采用合适的算法进行回收。新生代中对象的创建率很高,死亡率也很高,所以适合采用复制算法,只要付出少量对象的复制成本就可以完成回收。而老年代中对象创建率低,而且存活率极高,所以使用标记-整理算法是在合适不过了。

备注:
java堆划分内存区域进行内存回收的方式可以更加细致一些,Eden Space、Survivor Space、Tenured Gen;
下面这里是一种比较形象容易记住的方式进行说明,来自分代回收通俗解释
1、一个人(对象)出来(new 出来)后会在Eden Space(伊甸园)无忧无虑的生活,直到GC到来打破了他们平静的生活。GC会逐一问清楚每个对象的情况,有没有钱(此对象的引用)啊,因为GC想赚钱呀,有钱的才可以敲诈嘛。然后富人就会进入Survivor Space(幸存者区),穷人的就直接kill掉。
2、 大对象直接近入老年代-养老区:有些世界土豪出生。 他父母直接砸了几百亿, 身份显赫, 进入老年代,有钱就是嚣张!不用去Eden Space(伊甸园)。
3、Survivor Space(幸存者区有两个区域:生活区和无人区)为什么有两个区 from Survivor(生活区) ,to Survivor(无人区) .每次GC想要去幸存者区敲诈 ,会去from Survivor(生活区的)所有人带到to Survivor(无人区) ,然后开始敲诈, 被敲诈包括本次15次 的土豪,进入养老区,交不起保护费 的杀死, 没满足15 次,但是手里还有点钱的就生活在无人区,这个无人区就变成了生活区, 以前的生活区(人都被移走了) 又变成了无人区
4、并不是进入Survivor Space(幸存者区)后就保证人身是安全的,但至少可以活段时间。
GC会定期(可以自定义)会对这些人进行敲诈,亿万富翁每次都给钱,GC很满意,就让其进入了Genured Gen(养老区)。
(每经过一次Minor GC,会给这个富翁添加一次记录,当某些富翁连续给了大概15 年保护费,就可以去养老区了)万元户经不住几次敲诈就没钱了,GC看没有啥价值啦,就直接kill掉了。
5、进入到养老区的人基本就可以保证人身安全啦,但是亿万富豪有的也会挥霍成穷光蛋,只要钱没了,GC还是kill掉。

扩展:垃圾收集器

算法是内存回收的方法论,而垃圾收集器就是内存回收的具体实现了,JAVA虚拟机规范中没有任何关于垃圾收集器该如何实现的规定,因此不同厂商,不同版本实现的垃圾收集器有很大差别。下图是1.6HotSpot JVM的垃圾收集器。
深入JAVA虚拟机之垃圾收集_第1张图片

备注:
如果两个垃圾收集器之间有联系说明,它们可以配合使用。各个垃圾收集器的实现内容自行查阅资料,我就不再这里码字。下面是关于垃圾收集相关的一些参数,对于调优具有很好的作用。
深入JAVA虚拟机之垃圾收集_第2张图片