一般判断对象是否已经“死去”有两种方法,一个是 引用计数法
,还有一个是 可达性分析法
。
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能在被使用的。
而java虚拟机中并不是通过引用计数算法来判断对象是否存活的。
通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链(就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。
在java语言中,可作为GC Roots的对象包括下面几种:
有时我们可能有这样的需求:当内存空间还足够时,则能保留在内存之中;当内存空间在进行垃圾收集后还是非常紧张,则可以抛弃这些对象。
jdk 1.2之后,java引入了4中引用即:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)。下面简单介绍这四种引用.
最基础的收集算法是“标记-清除”(Mark-Sweep)算法,算法分为“标记”和“清除”两个阶段:标记出所有需要回收的对象,在标记完成后统一回收。这里的标记通过可达性分析算法来标记。
这个方法存在两个不足:一个是效率问题,标记和清除两个过程的效率都不高;另一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后分配大对象时,无法找到足够的连续内存而不得不提前触发另一个垃圾收集动作。
为了解决效率问题,它将可用内存容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完之后,就将还活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
现代的商业虚拟机都采用这种收集算法来回收新生代,将内存划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。
根据老年代的特点,一般采用“标记-整理”(Mark-Compact)算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
当前商业虚拟机的垃圾收集都采用“分带收集”(Generational Collection)算法,根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代,根据各个年代的特点采用最适当的收集算法。即在新生代中,每次垃圾收集时都会有大批对象死去,只有少量存活,选用复制算法。对于老年代中对象存活率很高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或者“标记整理”算法进行回收。
如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。这里主要介绍CMS收集器和G1收集器。
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它的运作过程分为4个步骤:
其中,初始标记、重新标记这两个步骤仍然需要“Stop The World”。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段就是进行GC Roots Tracing的过程,而重新标记阶段则是为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始化标记阶段稍长一些,但远比并发标记的时间短。
G1(Garbage-First)收集器是一款面向服务端应用的垃圾收集器。G1的特点如下:
在G1之前的其它收集器进行收集的范围都是整个新生代或者老年代,而G1不再是这样。
G1收集器的运作大致可划分为以下几个步骤:
java技术体系中所提倡的自动内存管理最终可以归结为自动化的解决两个问题:给对象分配内存以及回收分配给对象的内存。
大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC。
所谓的大对象是指,需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串以及数组。
虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别哪些对象应该在新生代,哪些对象应放在老年代中。为了做到这一点,虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且对象的年龄设为1。对象每经过一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁),就将会被晋升到老年代中。
为了能更好的适应不同程序的内存状况,虚拟机并不是永远地要求对象的年龄必须达到Max TenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。