GC主要做了两个工作,一个是内存的划分和分配,另一个是对垃圾进行回收。
关于内存的划分和分配,目前Java虚拟机内存的划分是依赖于GC设计的,比如现在GC都是采用了分代收集算法来回收垃圾的,Java堆作为GC主要管理的区域,被细分为新生代和老年代,再细致一点新生代又可以划分为Eden空间、From Survivor空间、To Survivor空间等,这样划分是为了更快地进行内存分配和回收。空间划分后,GC就可以为新对象分配内存空间。
关于对垃圾进行回收,被引用的对象是存活的对象,而不被引用的对象是死亡的对象(也就是垃圾),GC要区分出存活的对象和死亡的对象(也就是垃圾标记),并对垃圾进行回收。
垃圾回收标记方法
目前有两种垃圾标记算法,分别是引用计数算法和根搜索算法。
这两个算法都和引用有些关联,先回顾一下引用的知识点。
在JDK1.2之后,Java将引用分为强引用、软引用、弱引用和虚引用。
1.强引用
当我们新建一个对象时就创建了一个具有强引用的对象,如果一个对象具有强引用,垃圾收集器就绝不会回收它。Java虚拟机宁愿抛出OutOfMemoryError异常,使程序异常终止,也不会回收具有强引用的对象来解决内存不足的问题。2.软引用
如果一个对象只具有软引用,当内存不够时,会回收这些对象的内存,回收后如果还是没有足够的内存,就会抛出OutOfMemoryError异常。Java提供了SoftReference类来实现软引用。3.弱引用
弱引用比起软引用具有更短的生命周期,垃圾收集器一旦发现了只具有弱引用的对象,不管当前内存是否足够,都会回收它的内存。Java提供了WeakReference类来实现弱引用。4.虚引用
虚引用并不会决定对象的生命周期,如果一个对象仅持有虚引用,这就和没有任何引用一样,在任何时候都可能被垃圾收集器回收。一个只具有虚引用的对象,被垃圾收集器回收时会收到一个系统通知,这也是虚引用的主要作用。Java提供了PhantomReference类来实现虚引用。
引用计数算法
引用计数算法的基本思想就是每个对象都有一个引用计数器,当对象在某处被引用的时候,它的引用计数器就加1,引用失效时就减1。当引用计数器中的值变为0,则该对象就不能被使用,变成了垃圾。
目前主流的Java虚拟机没有选择引用计数算法来为垃圾标记,主要原因是引用计数算法没有解决对象之间相互循环引用的问题。
根搜索算法
根搜索算法基本思想就是选定一些对象作为GC Roots,并组成根对象集合,然后以这些GC Roots的对象作为起始点,向下搜索,如果目标对象到GC Roots是连接着的,我们则称该目标对象是可达的,如果目标对象不可达则说明目标对象是可以被回收的对象。
在Java中,可以作为GC Roots的对象主要有以下几种:
■ Java栈中引用的对象。
■ 本地方法栈中JNI引用的对象。
■ 方法区中运行时常量池引用的对象。
■ 方法区中静态属性引用的对象。
■ 运行中的线程。
■ 由引导类加载器加载的对象。
■ GC控制的对象。
垃圾收集算法
标记—清除算法
标记—清除算法(Mark-Sweep)是一种常见的基础垃圾收集算法,它将垃圾收集分为两个阶段。
■ 标记阶段: 标记出可以回收的对象。
■ 清除阶段: 回收被标记的对象所占用的空间。
标记—清除算法主要有两个缺点,一个是标记和清除的效率都不高,另一个就是容易产生大量不连续的内存碎片,碎片太多可能会导致后续没有足够的连续内存分配给较大的对象,从而提前触发新的一次垃圾收集动作。
复制算法(新生代)
它把内存空间划为两个相等的区域,每次只使用其中一个区域。在垃圾收集时,遍历当前使用的区域,把存活对象复制到另外一个区域中,最后将当前使用的区域的可回收的对象进行回收。
标记—压缩算法(老年代)
标记—清除算法可以应用在老年代中,但是它效率不高,在内存回收后容易产生大量内存碎片。因此就出现了一种标记—压缩(Mark-Compact)算法,与标记—清除算法不同的是,在标记可回收的对象后将所有存活的对象压缩到内存的一端,使它们紧凑地排列在一起,然后对边界以外的内存进行回收,回收后,已用和未用的内存都各自一边。
标记—压缩算法解决了标记—清除算法效率低和容易产生大量内存碎片的问题,它被广泛应用于老年代中。
分代收集算法
Java 堆区基于分代的概念,分为新生代(Young Generation)和老年代(Tenured Generation),其中新生代再细分为Eden空间、From Survivor空间和To Survivor空间。因为Eden空间中的大多数对象生命周期很短,所以新生代的空间划分并不是均分的,HotSpot虚拟机默认Eden空间和两个Survivor空间的所占的比例为8:1。
分代收集:
根据Java堆区的空间划分,垃圾收集的类型分为两种,它们分别如下。
■ Minor Collection: 新生代垃圾收集。
■ Full Collection: 对老年代进行收集,又可以称作Major Collection,Full Collection通常情况下会伴随至少一次的Minor Collection,它的收集频率较低,耗时较长。
当执行一次Minor Collection时,Eden空间的存活对象会被复制到To Survivor空间,并且之前经过一次Minor Collection并在From Survivor空间存活的仍年轻的对象也会复制到To Survivor空间。
有两种情况Eden空间和From Survivor空间存活的对象不会复制到To Survivor 空间,而是晋升到老年代。一种是存活的对象的分代年龄超过-XX:MaxTenuringThreshold(用于控制对象经历多少次Minor GC才晋升到老年代)所指定的阈值。另一种是To Survivor空间容量达到阈值。当所有存活的对象被复制到To Survivor空间,或者晋升到老年代,也就意味着Eden空间和From Survivor空间剩下的都是可回收对象。
这个时候GC执行Minor Collection,Eden空间和From Survivor空间都会被清空,新生代中存活的对象都存放在To Survivor空间。接下来将From Survivor空间和To Survivor空间互换位置,也就是此前的From Survivor 空间成为了现在的To Survivor 空间,每次Survivor空间互换都要保证To Survivor空间是空的,这就是复制算法在新生代中的应用。在老年代则会采用标记—压缩算法或者标记—清除算法。