我们就以内存回收,垃圾收集算法以及jvm中的垃圾收集器原理和特点三个部分来讨论。
一、内存回收
1、哪些内存需要回收?
我们知道Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,这些区域都有各自的用途。运行时
数据区可分为5个数据区域,它们分为是方法区、堆、虚拟机栈、本地方法栈、程序计数器。
其中程序计数器、虚拟机栈、本地方法栈这3个区域是线程隔离的,它们随线程而生,随线程而灭,它们在方法或者线程结束时,内存
就自动被回收了,所以垃圾收集器(Garbage Collection,GC)就不需要考虑它们的回收问题了。
但是相反地,内存中的Java堆和方法区则是所有线程共享的内存区域,这部分内存的分配和回收都是动态的,垃圾收集器所关注的是这部分内存。
2、判断对象是否还存活
1)引用计数算法(Reference Counting)
基本思想:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻
计数器为0的对象就是不可能再被使用的。
算法优点:实现简单、判定效率高。
缺点:主流的Java虚拟机里面没有选用引用计数算法来管理内存,其中最主要的原因是它很难解决对象之间相互循环引用的问题。
2)可达性分析算法(Reachability Analysis)
在主流的商用程序语言(Java、C#等)的主流实现中,都是通过可达性分析算法来判定对象是否存活的。
基本思想:通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为引用链(Reference
Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
在Java语言中,可作为GC Roots的对象包括下面几种:
①虚拟机栈(栈帧中的本地变量表)中引用的对象;
②方法区中类静态属性引用的对象;
③方法区中常量引用的对象;
④本地方法栈中JNI(即一般所说的Native方法)引用的对象。
3)四种引用
无论是通过引用计数算法还是可达性分析算法,它们判断对象是否存活都与“引用”有关。
在JDK1.2之后,Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(
Weak Reference)、虚引用(Phantom Reference)4 种,这 4 种引用强度依次减弱。
①强引用就是指在程序代码之中普遍存在的,类似“Object obj = new Object()”这类的引用,只要强引用还在,永远不会被垃圾收
集器回收。
②软引用是用来描述一些还有用但并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出之前,将会把这些对象列
进回收范围之中进行二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。在JDK1.2之后,提供了SoftReference类
来实现软引用。
③弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前
。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK1.2之后,提供了WeakReference类来
实现弱引用。
④虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影
响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系
统通知。在JDK1.2之后,提供了PhantomReference类来实现虚引用。
4)两次标记
即使在可达性分析算法中不可达的对象,也不是“非死不可”的,这时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要
经历两次标记过程:
如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对
象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法或finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没
有必要执行”。
如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会放置在一个叫做F-Queue的队列之中,并在稍后虚拟机自动建立
一个低优先级的Finalizer线程去执行它。这里所谓的“执行”是指虚拟机会触发这个方法,但并不承诺会等待它运行结束,这样做的原
因是,如果一个对象在finalize()方法中执行缓慢,或者发生了死循环,将很可能会导致F-Queue队列中其他对象永久处于等待,甚至
导致整个内存回收系统崩溃。finalize()方法是对象逃脱死亡的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模的标
记,如果对象要在finalize()中成功拯救自己——只要重新于引用链上的任何一个对象建立关联即可。如果对象这时候还没有逃脱,那
基本上它就真的被回收了。
5)方法区的回收
方法区(也就是HotSpot虚拟机中的永久代)主要回收两部分内容:废弃常量和无用的类。
废弃常量指的是没有被任何地方引用的常量。
无用的类则需要同时满足下面3个条件:
①该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例;
②加载该类的ClassLoader已经被回收;
③该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
二、垃圾收集算法
1)标记清除算法(Mark-Sweep)
标记清除算法是一种最基础的收集算法。
算法思想:算法分“标记”和“清除”两个阶段:首先标记(上述的标记过程)出所有需要回收的对象,在标记完成之后统一回收所有被
标记的对象。
缺点:
①效率不高;
②产生大量不连续的内存碎片。(如果新分配的对象无法找到连续内存,就会提前触发另一次垃圾收集动作)。
2)复制算法(Copying)
算法思想:它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当其中一块的内存用完了,就将还存活着的对象
复制到另一块上面,然后再把已使用过的内存空间一次性清理掉。
优点:实现简单,运行高效。
确定:将内存缩小为了原来的一半。
注:还有另一种划分方式,划分成一块较大的Eden空间和两块较小的Survivor空间,HotSpot虚拟机默认Eden和Survivor的大小比例是8:1。
3)标记整理算法(Mark-Compact)
算法思想:首先标记出需要回收的对象,然后让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
缺点:效率低,不适合老年代。(对象分为新生代和老年代)
4)分代收集算法(Generational Collection)
当前商业虚拟机的垃圾收集都采用“分代收集”算法,这种算法并没有什么新的思想,只是根据对象存活周期的不同将内存划分为几块
。一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
三、垃圾收集器
不同厂商、不同版本的虚拟机所提供的垃圾收集器都可能会有很大差别,并且一般都会提供参数供用户根据自己的应用特点和要求
组合出各个年代所使用的收集器。
1)Serial收集器
Serial收集器是最基本、最古老的收集器,是一个单线程收集器,在它进行垃圾收集时,必须暂停其他所有工作线程。
Serial收集器采用的是复制算法,它是虚拟机运行在Client模式下的默认新生代收集器。
优点:简单高效。
缺点:会造成用户线程的停顿。
2)ParNew收集器
ParNew收集器是Serial收集器的多线程版本,使用多个线程进行垃圾收集,是一个并行收集器。
ParNew收集器是在Server模式下的虚拟机中首选的新生代收集器,其中有一个重要原因是,除了Serial收集器外,目前只有它能
于CMS收集器配合工作
3)Parallel Scavenge收集器
Parallel Scavenge收集器是一个新生代收集器,采用复制算法,是一个并行收集器,它的关注点和其他收集器不同,目的是达到
一个可控制的吞吐量(Throughput),是一个“吞吐量优先”收集器。
吞吐量 = 运行用户代码时间/(运行用户时间+垃圾收集时间)。
4)Serial Old收集器
Serial Old收集器是Serial收集器的老年代版本,它同样是一个单线程收集器,使用“标记-整理”算法。
5)Parallel Old收集器
Patallel Old收集器是Parallel Scavenge收集器的老年代版本,同样是多线程收集器,使用“标记-整理”算法。可配合Parallel
Scavenge收集器一起使用在注重吞吐量以及CPU资源敏感的场合。
6)CMS收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。CMS收集器非常适合于B/S系统的服务端。是一个并发收集器。
CMS收集器是基于“标记-清除”算法实现的,整个过程分4个步骤:
①初始标记;
②并发标记;
③重新标记;
④并发清除。
缺点:
①CMS收集器对CPU资源非常敏感;
②CMS收集器无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生;
③由于使用“标记-清除”算法,产生大量不连续的内存碎片。(如果新分配的对象无法找到连续内存,就会提前触发另一次垃圾收集动作)。
7)G1收集器
G1(Garbage-First)收集器是当今收集器技术发展的最前沿成果之一,G1是一款面向服务端应用的垃圾收集器,使命是(在比较长期的)未来可以替换掉JDK1.5中发布的CMS收集器。
G1收集器特点:
①并行与并发;
②分代收集;
③空间整合;
④可预测的停顿。
G1收集器的运作步骤:
①初始标记;
②并发标记;
③最终标记;
④筛选回收。