给对象添加一个引用计数器,当有一个地方引用它时,值加1;当引用失效时,值减1;任何时候计数器为0的对象就是不可能再被使用的。
但主流的JVM没有选用引用计数法来管理内存,因为很难解决对象之间相互循环引用的问题
通过一系列称为“GC Root”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则此对象是不可用的。
在上图中,虽然obj5,obj6和obj7之间存在引用,但它们到GC Roots是不可达的,所以会被判定为可回收对象
Java中,可作为GC Roots对象包括下面几种:
引用分类的目的:当内存空间还足够时,则保留在内存中;如果内存空间在进行GC后还是很紧张,则可以抛弃。
因此,将引用分为:
即使在可达性分析算法中不可达的对象,此时也只是处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程:
可达性分析后发现没有与GC Roots相连接的引用链,将会被第一次标记并且进行一次筛选,条件是此对象是否用必要执行finalize()。
当对象没有覆盖finalize()或finalize()已经被JVM调用过,JVM将这两种情况视为“没有必要执行”。
若该对象被判定有必要执行finalize(),那么这个对象将会放置在一个叫做F-Queue的队列中,并在稍后有一个JVM自动建立的、低优先级的Finalizer线程去执行。
这里的的“执行”是指JVM会触发这个方法,但并不承诺会等待它运行结束,因为:若一个对象在finalize()中执行缓慢,或发生死循环,很可能导致F-Queue中其他对象永久处于等待,甚至导致整个内存回收系统崩溃。
如果对象要在finalize()中拯救自己,只要重新与引用链上的任何对象建立关联即可,比如把this关键字赋值给某个变量或者对象的成员变量,那在第二次标记时它将被移除“即将回收”的集合。
永久代的GC主要回收两部分:
算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
不足:
将可用内存按容量大小分为大小相等的两块,每次只使用其中一块。当这一块的内存用完了,就将还存活着的对象复制到另一块上,然后再把已使用过的内存空间一次清理掉。
代价是将内存缩小为原来的一半。
标记过程与“标记-清除”算法一样,但后续步骤是让所有存活对象都像一端移动,然后直接清理掉端边界以外的内存。
根据对象存活周期不同将内存划分为几块。
一般是分为:
一个单线程收集器,进行垃圾收集时,必须暂停其他所有工作线程,直到它收集结束。
JVM在Client模式下的默认新生代收集器,与其他收集器的单线程比简单而高效,对于限定单个CPU环境,没有线程交互的开销
Serial收集器的多线程版本。
许多JVM在Server模式下中首选的新生代收集器,因为目前除了Serial收集器外,只有它能与CMS配合工作
新生代收集器,使用复制算法,并行的多线程收集器。
目标是达到一个可控制的吞吐量。
吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)
高吞吐量可以高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务
GC自适应调节策略:JVM根据当前系统的运行情况收集性能监控信息,动态调整参数以提供最合适的停顿时间或最大吞吐量
Serial收集器的老年代版本,单线程收集器,使用“标记-整理”算法。
主要意义:Client模式下的JVM使用
Server模式下用途:
Parallel Scanvenge收集器的老年代版本,使用多线程和“标记-整理”算法
与Parallel Scanvenge配合使用
以获取最短回收停顿时间为目标,有并发收集、低停顿的优点
基于“标记-清除”算法,整个过程分为四个步骤:
其中,初始标记、重新标记仍然需要“Stop The World”。
耗时最长的并发标记与并发清除过程,收集器线程都可以与用户线程一起工作,所以,总体上来说,CMS收集器的内存回收是与用户线程一起并发执行的。
缺点:
Garbage-First收集器面向服务端应用,具备以下特点:
G1中将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,他们都是一部分Region(不需要连续)的集合。
G1跟踪各个Region中垃圾堆积的价值大小(回收所获得的空间预计回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。这种方式保证了G1在有限的时间内可以获取尽可能高的效率。
G1的运作大致可划分为以下几个步骤:
大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够空间时,JVM将发起一次Minor GC
新生代GC(Minor GC):指发生在新生代的GC,因对象大多都朝生夕灭,所以MinorGC非常频繁,一般回收速度也比较快
老年代GC(Major GC / Full GC):指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对)。Major GC的速度一般比Minor GC慢10倍以上
大对象指需要大量连续内存空间的对象
JVM给每个对象定义了一个对象年龄计数器。
如果对象在Eden出生并经过第一次Minor GC后仍存活,且能被Survivor容纳的话,将被移动到Survivor空间,并将对象年龄设为1。对象在Survivor区中每“熬过”一次Minor GC,年龄就增加1岁,当年龄增加到一定程度(默认15,可设置),就会被加入到老年代中。
为了更好地适应不同程序的内存状况,JVM并不是永远要求对象年龄必须达到一定值才能进入老年代,如果Survivor中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可直接进入老年代。
在发生Minor GC前,JVM会先检查老年代最大可用连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC是安全的;
如果不成立,则JVM会查看HandlePromotionFailure设置值是否允许担保失败
如果允许,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小
如果大于,将尝试进行一次Minor GC(有风险)
如果小于,或者HandlePromotionFailure设置不允许冒险,此时也要改为进行一次Full GC