虚拟机的程序计数器,虚拟机栈和本地方法栈属于线程私有,随线程创建而创建,随线程结束而结束,这几个区域的内存分配和回收都具有确定性。因此虚拟机内存回收主要考虑堆区和方法区。
回收算法:
1、引用计数算法:
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值加1;引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。(没有被使用,很难解决对象之间相互循环引用的问题)
2、可达性分析算法:
通过一系列的成为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径成为引用链,当一个对象到GC Roots没有任何引用链相连,则证明此对象是不可用的,它们将会被判定为可回收的对象。
可作为GC Roots的对象包括:
虚拟机栈(栈帧中的本地变量表)中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象、本地方法栈中JNI(native方法)引用的对象。
如果对象在进行在进行可达性分析后发现没有与GC Roots相连接的引用链,则会被第一次标记并进行筛选(筛选条件为对象覆盖了finalize()方法或者对象没有执行过finalize())。筛选后对象会被放入F-Queue的队列中,队列中的对象会执行finalize方法,如果在执行finalize方法时和引用链上的对象相关联,则会被移出不被GC回收。反之就会被回收。如果进入F-Queue前已经执行过一次finalize方法,则也会被回收。
3、标记-清除算法:
首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象(标记不可达的对象)。
缺点:1、标记和清除效率都不高 2、会产生大量不连续的内存碎片。
4、复制算法:
内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块内存用完了,就将还存活的对象复制到另一块上面,然后再把已使用的内存空间一次清理掉。在
JVM的HotSpot新生代区划分为8:1:1的空间,依次为Eden、From (Survivor)、To(Survivor)区。新生代的对象会首先在Eden区,在gc时存活的对象移动到From区,并计算年龄。下一次gc时Eden和From区都会被清空,里面的对象会被移动到To区,在To区被填满后,会将对象移动到老年代。也有可能在survivor区空间不够的时候,通过内存担保机制直接将对象移动到老年代。
5、标记-整理算法:
标记出所有需要回收的对象后,让所有存货的对象都向一段移动,然后直接清理掉端边界以外的内存。
垃圾收集器:
1、Serial收集器:
单线程收集器,在进行垃圾收集时,会暂停其他所有工作线程。
2、ParNew收集器
Serial收集器的多线程版本
3、Parallel Scavenge收集器
设计目标是达到一个可控制的吞吐量。
4、Serial Old收集器
Serial收集器的老年代版本,使用标记-整理算法。
5、Parallel Old收集器
Parallel Scavenge收集器的老年代版本,使用标记-整理算法。
6、CMS收集器
CMS(Concurrent Mark Sweep)
包含四个过程:初始标记、并发标记、重新标记、并发清除。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段就是进行GC Roots Tracing的过程,重新标记阶段为了修正并发期间用户程序继续运作而导致标记产生变动的那一部分对象的标记记录(等于是边工作边清理,内存回收过程和用户线程并发执行)。
缺点:
1、对CPU资源非常敏感,默认启动线程数是(CPU数量+3)/4。在双核CPU的情况下,等于需要占用一半的cpu资源。
2、无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败而导致一次Full GC发生。这是因为并发标记阶段还会产生垃圾,CMS收集器无法在当此收集中处理掉它们,只好留待下一次GC时清理掉。也就是说在并发收集时还需要预留空间给用户线程使用,如果空间不够,则有可能会产生Full GC。JDK 1.5时设置的老年代超过68%空间会激活“Concurrent Mode Failure”,JDK1.6时设置为92%
3、由于采用标记-清除算法,会产生大量的空间碎片,在大对象分配空间时,会触发Full GC。CMS提供-XX:+UseCMSCompactAtFullCollection开关参数(默认开启),用于空间不够需要Full GC时开启合并整理过程。
7、G1收集器:
特点:1、并行并发 2、分代收集 3、空间整合,与CMS收集器不同,采用标记-整理算法 4、可预测的停顿,建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。