判断对象是否存活的相关算法
1.引用计数算法
引用计算算法核心是,对象每多一个引用,则计数值加1,当引用失效时,计数器值就减1;当计数值为0时,认为对象无用。
但是其中会出现循环引用问题,既是A引用B,B引用A。计数值不为0,对象无用,但无法被回收。
2.可达性分析算法
这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。如图3-1所示,对象object 5、object 6、object 7虽然互相有关联,但是它们到GC Roots是不可达的,所以它们将会被判定为是可回收的对象。
虚拟机栈(栈帧中的本地变量表)中引用的对象。
方法区中类静态属性引用的对象。
方法区中常量引用的对象。
本地方法栈中JNI(即一般说的Native方法)引用的对象。
强引用:通过new创建的对象,只要引用还存在,不会被回收。
软引用:在JDK 1.2之后,提供了SoftReference类来实现软引用。在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。
弱引用:在JDK1.2之后,提供了WeakReference类来实现弱引用。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
虚引用:在JDK 1.2之后,提供了PhantomReference类来实现虚引用。为一个对象设置虚引用关联的唯一目的就是能在这
个对象被收集器回收时收到一个系统通知。
对象回收的相关算法
标记-清除算法(Mark-Sweep)最基础的收集算法,通过对无用对象进行标记,在回收时,一次性回收所有被标记对象。
它的主要不足有两个:一个是效率问题,标记和清除两个过程的效率都不高;
另一个是空间问题,标记清除之后会产生大量不连续的内存碎片。
复制算法(Copying)
核心是把内存分为相等的两块,把对象放在其中的一块,当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
优点是实现简单,运行高效。缺点是实际可用内存只为原来的一半。
标记-整理算法(Mark-Compact)
该算法其实是标记清除算法的变形,把存活对象标记起来,在回收时,把存活对象向一侧移动,然后以此为界,清除另外一边的所有对象。
分代收集算法(Generational Collection)
当前商业虚拟机的垃圾收集都采用该算法,这种算法并没有什么新
的思想,只是根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代,这样就可以根据
各个年代的特点采用最适当的收集算法。
在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法只需要付出少量存活对象的复制成本就可以完成收集。
而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清理”或者“标记—整理”算法来进行回收。
新生代将内存分为一块较大的Eden空间和两块较小的Survivor(form和to)空间,每次使用Eden和其中一块Survivor [1] 。当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8:1,也就是每次新生代中可用内存空间为整个新生代容量的90%(80%+10%),只有10%的内存会被“浪费”。当Survivor空间不够用时,需要依赖其他内存(这里指老年代)进行分配担保(Handle Promotion)。
分配担保,如果另外一块Survivor空间没有足够空间存放上一次新生代收集下来的存活对象时,这些对象将直接通过分配担保机制进入老年代。
垃圾收集器
Serial收集器
这个收集器是一个单线程的收集器,但它的“单线程”的意义并不仅仅说明它只会使用
一个CPU或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,
直到它收集结束。
核心使用的复制算法。目前还是client模式下新生代的默认收集器。
ParNew收集器
ParNew收集器其实就是Serial收集器的多线程版本.只是有多个GC线程。是server模式下常用算法。
使用ParNew的一个很重要的原因是除了Serial收集器外,目前只有它能与CMS收集器配合工作。
CMS收集器是HotSpot虚拟机中第一款真正意义上的并发(Concurrent)收集器。
●并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
●并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户
程序在继续运行,而垃圾收集程序运行于另一个CPU上。
Parallel Scavenge收集器
Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法的收集器,是并行的多线程收集器。
Parallel Scavenge收集器的特点是它的关注点与其他收集器不同,CMS等收集器的关注点是尽可能地缩短垃圾
收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量(Throughput)。
Serial Old收集器
Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用“标记-整理”算法。这个收集
器的主要意义也是在于给Client模式下的虚拟机使用。如果在Server模式下,那么它主要还有两大用途:一种用途
是在JDK 1.5以及之前的版本中与Parallel Scavenge收集器搭配使用 ,另一种用途就是作为CMS收集器的后备预
案,在并发收集发生Concurrent Mode Failure时使用。
Parallel Old收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。可以搭配Parallel Scavenge使用。
总结:以上的几种收集器可以看出一个特点,新生代使用复制算法,老年代使用标记整理算法的特点。
CMS收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的
Java应用集中在互联网站或者B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,
以给用户带来较好的体验。CMS收集器就非常符合这类应用的需求。
它的运作过程相对于前面几种收集器来说更复杂一些,整个过程分为4个步骤,包括:
初始标记(CMS initial mark)(stop the world)标记GC Roots能直接关联到的对象
并发标记(CMS concurrent mark) 并发标记阶段进行GC RootsTracing
重新标记(CMS remark)(stop the world) 修正并发标记错误
并发清除(CMS concurrent sweep)
CMS收集器无法处理浮动垃圾(Floating Garbage),可能出现“Concurrent Mode Failure”失败而导致另一
次Full GC的产生。由于CMS并发清理阶段用户线程还在运行着会产生新的垃圾。
基于标记-清除算法,会产生不连续空间。
G1收集器
G1具备如下特点:
并行与并发:G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短Stop-
The-World停顿的时间,部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让Java程序继续执行。
分代收集:与其他收集器一样,分代概念在G1中依然得以保留。虽然G1可以不需要其他收集器配合就能独立管
理整个GC堆,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更
好的收集效果。
空间整合:与CMS的“标记—清理”算法不同,G1从整体来看是基于“标记—整理”算法实现的收集器,从局
部(两个Region之间)上来看是基于“复制”算法实现的,但无论如何,这两种算法都意味着G1运作期间不会产生
内存空间碎片,收集后能提供规整的可用内存。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到
连续内存空间而提前触发下一次GC。
可预测的停顿:这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿
外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上
的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了。
使用G1收集器时,Java堆的内存布局就与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。
G1收集器后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。
在G1收集器中,使用Remembered Set来避免全堆扫描的(判断可达性时,对象可能不在一个Region)。
G1中每个Region都有一个与之对应的Remembered Set,虚拟机发现程序在对
Reference类型的数据进行写操作时,会产生一个暂时中断写操作,检查Reference引用的对象是否处于不同的Region之中如果是,便通过CardTable把相关引用信息记录到被引用对象所属的Region的Remembered Set之中。当进行内存回收时,在GC根节点的枚举范围中加入Remembered Set即可保证不对全堆扫描也不会有遗漏。
G1收集器的运作大致可划分为以下几个步骤:
初始标记(Initial Marking)stop the world
并发标记(Concurrent Marking)
最终标记(Final Marking)stop the world
筛选回收(Live Data Counting and Evacuation)stop the world
初始标记阶段标记GC Roots能直接关联到的对象,并且修改TAMS的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象。
并发标记阶段是从GC Root开始对堆中对象进行可达性分析,找出存活的对象,可与用户程序并发执行。
而最终标记阶段则是为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机
将这段时间对象变化记录在线程Remembered Set Logs里面,最终标记阶段需要把Remembered Set Logs的数据合并到Remembered Set中,这阶段需要停顿线程,但是可并行执行。
最后在筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划,从Sun公司透露出来的信息来看,这个阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分Region,时间是用户可控制的,而且停顿用户线程将大幅提高收集效率。