一.前言
Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的围墙,墙外面的的人想进去,墙里面的人想出来。—《深入理解Java虚拟机》
二.概述
首先我们需要理解我们为什么需要理解垃圾回收和内存分配策略,Java经过这么多年的发展,相关的技术已经相当成熟,那我们为何还要去了解垃圾回收呢?原因很简单,当我们需要排查各种内存泄漏和内存溢出问题时,或者当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些技术实施必要的管理和调节,如果你不熟悉和了解,那么它就会阻碍你程序性能的提升。
三.哪些内存需要回收?
我们都知道堆存放着Java世界里面几乎所以的对象实例,垃圾收集器在回收他们的时候,第一步就需要确定哪些是可以回收的,哪些不可以。
3.1 引用计数法
引用计数法:在对象中添加一个引用计数器,每当有其他对象引用时,计数器加一,当引用失效时,计数器的值减一。当计数器为0的时候就说明没有其他对象引用,对象已死。(Java虚拟机不采用这种方法回收垃圾)
3.2 可达性分析法(又称跟搜索法)
Java的内存管理系统,是通过可达性分析法赖判断对象是否存活。基本思想如下:
通过一系列的称为”GC Roots“的跟对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走的路径被称为引用链。如果某个对象到GC Roots之间没有任何引用链,则此对象可被回收。
在Java体系中,可以作为GC Roots的对象有许多种,感兴趣的可以再去了解,这里不过多赘述。另外在可达性分析算法中被判断为不可达的对象,其实也不是一定要被回收。其实还需要进行二次判断。这里涉及到finalize( )方法,大家做一个了解即可。
3.3 引用的分类
在讨论引用的分类之前,,我们需要再对引用进行了解。首先我们都知道内存是有限的,那么我们是不是可以发明这样一种对象,当内存足够的时候,我们不对它进行回收,当内存空间不足、或者内存空间进行垃圾收集之后,空间仍然紧张,那我们就希望抛弃这些对象。
在JDK1.2版本之后,Java·对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用。这些引用的强度逐渐减弱。这种划分这是应对上述的空间紧张的情况。
强引用:无论任何情况下,只要强引用关系存在,垃圾收集器就永远不会回收被引用的对象。
软引用:软引用是描述一些还有用,但非必须的对象。只要被软引用关联的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之内进行二次回收,如果回收了仍然内存不够,才会抛出OOM。JDK1.2之后,通过SoftReference类来实现软引用。
弱引用:被弱引用管理的对象只能生存到下一次垃圾收集发生的时刻,当垃圾收集器开始工作后,被弱引用关联的对象会被全部回收。通过WeakReference类实现弱引用。
虚引用:为一个对象设置虚引用关联的唯一目的就是在这个对象被垃圾回收器回收之前可以收到一个系统通知。
三.3种垃圾回收算法
当代的Java虚拟机的垃圾收集器,大多数都遵循了“分代收集”的理论进行设计,并且根据《深入理解Java虚拟机》中的表述,它们建立在两个分代假说之上:
1)弱分代假说:绝大多数对象都是朝生熄灭的;
2)强分代假说:熬过越多次垃圾收集过程的对象就难以消亡;
这两个分代假说共同奠定了垃圾收集器的一致收集原则:收集器应该将Java堆划分出不同的区域,然后将回收对象依据其年龄【熬过的次数】分配到不同的区域存储。。在Java堆划分出不同的区域之后,垃圾收集求才可以每次只回收其中某一个或者某些部分的区域—因而才有了新生代收集,老年代收集,混合收集这样的回收类型的划分。
这是因为有了回收类型的划分,进而衍生出多种不同的垃圾回收算法:
1.标记—清除算法:
标记清除算法是最基础的垃圾回收算法,其划分为2个阶段:首先标记出所有需要回收的对象,在标记完成之后,统一回收被标记的对象,也可以标记存活的对象,回收未被标记的对象。之所以说它是最基本的算法,是因为后续的算法都是基于标记清除算法的缺点进而改进得到的。
缺点有两个:第一个是执行效率不稳定,如果Java堆中包含大量的对象,且其中大部分都是要回收的对象,此时就要进行大量的标记和清除动作,导致标记和清除的过程都随对象数量的增长而降低;第二个是在完成标记和清除之后,会存在大量的内存碎片,空间碎片太多的可能会导致当以后在程序运行过程中需要给对象分配较大内存时,找不到连续的可用空间,进而提前触发垃圾回收机制;
2.标记—复制算法:
复制算法将内存按照容量划分为大小相等的两块,每次只使用其中的一半,当一半用完了,就将这一半的存活的对象复制到另一半中去,同时在把已使用的这一半内存空间一次清除掉。
缺点:如果已经使用的这一半当中大多数内存对象都是存活的,那么就需要大量的复制操作,此外每次只能有效利用内存空间的一半,由点浪费。
值得注意的是,现在的商用Java虚拟机大多数都采用了这种标记—复制算法去回收新生代,因为新生代中的对象90%都熬不过第一轮收集,采用标记复制算法,每次只需要复制少量的存活对象就可以了。
3.标记—整理算法:
标记—整理算法的标记过程和标记—清除算法一样,但是标记—整理算法的后续过程不是直接对可回收对象进行清理,而是让不可回收的对象向内存空间的一侧进行移动。然后直接清理掉边界以外的内存。这种算法通常针对于老年代进行。值得注意的是,即使这个过程涉及大量的 移动操作,但是和标记清除导致的内存碎片相比,他仍然更加具有优势。
四.垃圾收集器
如果说垃圾回收算法是内存回收的理论,那么垃圾收集器就是内存回收的实践。
上图展示了七种作用与不同分代的收集器,如果两个收集器之间存在连线,就说明他们可以搭配使用,图中收集器所处的位置,代表他们是属于新生代收集器还是老年代收集器。下面我们重点会从收集器的目标、特性、原理、和使用场景分析上面最重要的两种收集器。
4.1 CMC收集器
CMC(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。基于标记清除算法实现,整个过程分为四步,包括:
1)初始标记
2)并发标记
3)重新标记
4)并发清除
4.2 Garbage First收集器(又称G1)
G1是主要面对服务端应用的收集器。它和其他收集器最主要的不同在于,其他收集器的垃圾收集范围要么是整个新生代,要么是整个老年代,要么是整个堆,但是G1收集器可以面向堆当中的任何一个区域回收,衡量标准不再是它属于哪一个分代,而是哪一块内存中垃圾存放数量多,就回收哪一块。