GC的算法与种类
引用计数法
引用计数器的实现很简单,对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1,当引用失效时,引用计数器就减1。只要对象A的引用计数器的值为0,则对象A就不可能再被使用。
【注】名词解释
可达对象:通过根对象进行引用搜索,最终可以到达的对象
不可达对象:通过根对象进行引用搜索,最终没有被引用到的对象
引用计数法的问题
引用和去引用伴随加法和减法,影响性能
很难处理循环引用
如图循环引用因为引用计数不为0,所以永远不会被GC。
标记-清除
标记-清除算法是现代垃圾回收算法的思想基础。标记-清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。一种可行的实现是,在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象。因此,未被标记的对象就是未被引用的垃圾对象。然后,在清除阶段,清除所有未被标记的对象。
标记清除存在一个问题,多次标记清楚过后容易形成过多的内存碎片
标记-压缩
标记-压缩算法适合用于存活对象较多的场合,如老年代。它在标记-清除算法的基础上做了一些优化。和标记-清除算法一样,标记-压缩算法也首先需要从根节点开始,对所有可达对象做一次标记。但之后,它并不简单的清理未标记的对象,而是将所有的存活对象压缩到内存的一端。之后,清理边界外所有的空间。
这种方式避免的内存碎片的产生
复制算法
复制算法将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。
与标记-清除算法相比,复制算法是一种相对高效的回收方法,但是复制算法需要多一倍的内存。
复制算法比较适用于新生代。因为在新生代,垃圾队形通常会多于存货对象,复制算法的效果会比价好。不适用于存活对象较多的场合如老年代。why?思考下
分代思想
对于复制、标记清除、标记压缩等GC算法各自都有优势和缺陷,在所有的算法中没有一种算法能够替代其他算法。因此根据垃圾回收对象的特性,使用合适的算法回收,才是明智的选择。
分代算法就是这种思想,它将内存区间根据对象的特点分成几块,根据每块内存区间的特点,使用不同的回收算法,以提高垃圾回收的效率。
通常情况下JVM虚拟机的堆分为新生代和老年代。新生代的特点是对象存活时间一般不长,大约90%的新建对象会被很快回收,因此,新生代比较适合使用复制算法。当一个对象经过几次回收之后依然存活,对象就会被放入老年代的内存空间。在老年代中,几乎所有对象都是经过几次GC之后依然得以存活的。因此,可以任务这些对象,在一定时期内,甚至在应用程序的整个生命周期中,将是常驻内存的。所以使用标记清除或者标记压缩算法比较合适。
分区思想
分带算法按照对象的生命周期长短划分为两个部分,分区算法将整个堆空间划分成连续的不同小区间,每个小区间独立使用独立回收。这种算法的好处是可以控制一次回收多少个区间。这样可以让GC导致停顿时间拆短,如此GC多次每次时间变短。
谁才是真正的垃圾——判断可触及性
所有的算法,需要能够识别一个垃圾对象,因此需要给出一个可触及性的定义:即从根节点开始是否可以访问到这个对象,如果可以,则说明当前对象正在被使用,如果从所有的根节点都无法访问到某个对象,说明对象已经不再使用了,一般来说,此对象需要被回收。
简单来说可触及性包括以下3种状态:
可触及的:从根节点可以开始,可以到达这个对象。
可复活的:对象的所有引用被释放,不过finalize()函数没有被调用,可能在finalize()函数中复活。
不可触及的:对象的finalize()函数被调用,并且没有复活。
以上三种状态只有对象在不可触及时才可以被回收。
【注:根节点】
栈中引用的对象
方法区中静态成员或者常量引用的对象(全局对象)
JNI方法栈中引用对象
【注:finalize】
- finalize()是Object的protected方法,子类可以覆盖该方法以实现资源清理工作,GC在回收对象之前调用该方法。
- finalize()与C++中的析构函数不是对应的。C++中的析构函数调用的时机是确定的(对象离开作用域或delete掉),但Java中的finalize的调用具有不确定性
- 不建议用finalize方法完成“非内存资源”的清理工作,但建议用于:① 清理本地对象(通过JNI创建的对象);②作为确保某些非内存资源(如Socket、文件等)释放的一个补充:在finalize方法中显式调用其他资源释放方法。其原因可见下文[finalize的问题]
但是finalize()依然是一个糟糕的模式,不推荐使用,因为finalize()可能发生引用外泄,无意中复活对象。更为重要的是finalize()是系统调用的,调用时间不明确,而try-catch-finally完全可以有更加好的效果。
参考finalize方法总结
对象的复活
举个例子:
引用的强弱
java中提供4个级别的引用:强引用、软引用、弱引用和虚引用
这里略过了….
Stop-The-World
Java中一种全局暂停的现象
全局停顿,所有Java代码停止,native代码可以执行,但不能和JVM交互
多半由于GC引起
1. Dump线程
2. 死锁检查
3. 堆Dump
危害
长时间服务停止,没有响应
遇到HA系统,可能引起主备切换,严重危害生产环境。