当需要排查各种内存溢出泄露问题时,垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。
GC需要完成三件事情:
- [ ] 哪些内存需要回收?
- [ ] 什么时候回收?
- [ ] 如何回收?
上一篇博客介绍了Java内存运行时区域的各个部分,其中程序计数器、虚拟机栈、本地方法栈随线程而生,随线程而灭,在这3个区域不用过多考虑回收问题。而Java堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期间才能知道创建了哪些对象,这部分的内存是动态分配和回收的,垃圾收集器所关注的是这部分的内存。
一、 如何判断对象已死?
我们都知道堆中几乎存放着所有对象的实例,GC在进行回收前,首先要判断这些实例哪些还“活着”哪些已经“死去”(不再被引用)。
如何判断对象是否已死有如下两种方法:引用计数算法和根搜索算法。
1.1引用计数算法
概念:给对象添加一个引用计数器,有引用就+1,引用结束就-1。当计数器为0就意味着对象已死。
不过JAVA中没有使用引用计数算法来管理内存,其中的原因是无法解决对象之间相互循环引用的问题
1.2根搜索算法
概念:这个算法的基本思路是通过一系列名为GC ROOTS的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径为引用链(Reference Chain),当一个对象到GC ROOTS没有任何引用链,则证明此对象是不可用的。
根搜索算法是从离散数学中的图论引入的,程序把所有引用关系看作一张图,从一个节点GC ROOT 开始,寻找对应的引用节点,找到这个节点后,继续寻找这个节点的引用节点。当所有的引用节点寻找完毕后,剩余的节点则被认为是没有被引用到的节点,即无用的节点。
GC Roots对象包括以下几种:
- [ ] 虚拟机栈(栈帧中的本地变量表)中的引用对象
- [ ] 方法区中的类静态属性引用对象
- [ ] 方法区中的常量引用对象
- [ ] 本地方法栈JNI的引用对象
在根搜索算法中,要真正宣告一个对象死亡,至少要经历两次标记过程:
1.如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有 覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”。
2.如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会放置在一个叫做F-Queue队列之中,并在稍后由一个由虚拟机自动建立的、低优先级的Finalizer线程去执行它。finalize()方法是对象逃脱死亡命运的最后一次机会,稍候GC将对F-Queue中的对象进行第二次小规模的标记,如果对象要在finalize()中成功拯救自己——只要重新与引用链上的任何一个对象建立关联即可,譬如把自己(this关键字)赋值给某个类变量或者对象的成员变量,那在第二次标记时它将会被移除出“即将回收”的集合;如果对象这时候还没有逃脱,那基本上它就真的被回收了。
finalize():垃圾回收器准备释放内存的时候,会先调用finalize()。
析构函数(destructor):与构造函数相反,当对象结束其生命周期时(例如对象所在的函数已调用完毕),系统自动执行析构函数。析构函数往往用来做“清理善后” 的工作(例如在建立对象时用new开辟了一片内存空间,delete会自动调用析构函数后释放内存)。
二、JAVA中引用的分类
对象不能仅仅被定义为引用和没被引用两种状态,我们希望当内存足够是,能保存在内存之中,如果内存在进行GC后仍然紧张,则可抛弃这些对象,很多系统的缓存功能都符合这样的应用场景。
于是JDK1.2之后,JAVA对引用的概念进行了扩充,四种引用级别由高到低分别为:强引用、软引用、弱引用和虚引用。
- [ ] 强引用(StrongReference):
平时我们编程的时候例如:Object object=new Object();那object就是一个强引用了。如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空 间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
- [ ] 软引用(SoftReference):
如果内存空间足够,垃圾回收器就不会回收它,如果内存 空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。 软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联 的引用队列中。
- [ ] 弱引用(WeakReference):
弱引用与软引用的区别在于:只具有弱引用的对象拥有更 短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联 的引用队列中。
- [ ] 虚引用(PhantomReference):
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象 仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。 虚引用主要用来跟踪对象被垃圾回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队 列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。