【JAVA进阶架构师指南】之四:垃圾回收GC
前言
在【java进阶架构师指南】系列二和三中,我们了解了jvm的内存模型以及类加载机制,其中在内存模型中,我们说到,从线程角度来说,jvm分为线程私有的区域(虚拟机栈/本地方法栈/程序计数器)和线程公有区域(方法区和java堆),其中线程私有区域内存随着线程的结束而跟着被回收,gc主要关注的是堆和方法区这部分的内存.
gc回收算法
gc如何确定哪些对象需要回收呢?一般而言,有两种算法:引用计数算法和可达性分析算法.
引用计数算法
为每个对象都持有一个引用计数器,初试状态为0,该对象每次被引用就加一,否则就减一,因此当gc进行垃圾回收的时候,判断如果该引用计数器=0则进行回收,否则不进行回收,显而易见,引用计数算法的缺点就是不能解决循环依赖的问题,假如对象a引用对象b,对象b引用对象c,对象c引用对象a,循环依赖导致abc三个对象都不能被回收.因此引出了可达性分析算法.
可达性分析算法
所谓可达性分析算法,就是通过一系列名为"gc roots"的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(reference chain),当一个对象到gc roots没有任何引用链相连时,则证明此对象是可以被回收的,反之则不可被回收,在java中,可被作为"gc roots"的对象包括如下几种:
a.虚拟机栈(栈桢中的本地变量表)中的引用的对象
b.方法区中的类静态属性引用的对象
c.方法区中的常量引用的对象
d.本地方法栈中jni的引用的对象
java语言对象引用类型
无论是引用计数算法还是可达性分析算法,都涉及到对象的引用,在java中,引用分为强引用、软引用、弱引用、虚引用(幽灵引用)4种,这四种引用强度依次逐渐减弱.所谓强引用,就是我们平时最常用的new一个新对象,比如:
object object = new object();
强引用的对象永远不会被gc回收,即使在内存不足的情况下,jvm宁愿抛出outofmemory错误也不会回收这种对象,因此,我们看许多优秀框架的源码的时候,经常会看到如下代码:
后面有注释//help gc, 将对象置为null,帮助gc进行垃圾回收,这里就是消除强引用,让无用对象的内存能被顺利回收.有兴趣的童鞋可以多多翻看优秀框架的源码,比如jdk/spring中肯定会有大量这样的写法,足见这些源码作者的态度之严谨,编程功力之深厚,值得我们学习!
而软引用、弱引用、虚引用这几类在jdk中都有对应的实现,分别对应softreference/weakreference/phantomreference,由于博客篇幅有限,不能所有知识点都讲得很详细,只能告诉童鞋们有这些知识点,有兴趣的童鞋可以自己下去学习了解.
gc回收策略
讲完了gc如何确定哪些对象需要回收之后,我们再来看看gc进行垃圾回收有哪些策略,一般而言,有三种:标记清除算法/复制算法/标记整理算法.
1.标记清除算法
标记清除算法是最基础的回收算法,分为标记和清除两个部分:首先标记出所有需要回收的对象,这一过程在可达性分析过程中进行.在标记完之后统一回收所有被标记的对象:
这种算法的缺点很明显,就是会产生大量不连续的内存碎片,导致经常无法分配出较大的内存,从而不得不经常触发垃圾回收.
2.复制算法
既然不能回收出连续的内存空间,那就从一开始就把内存划分为两个区,平时只用一个区域,当其中一个区域内存满了,触发gc时,找出无需回收的对象,将它们全部转移到另一块未使用的区域,并且整理到一起使之连续,如此循环,这就是复制算法:
复制算法改善了标记清楚算法中内存碎片不连续的缺点,但是它的缺点也很明显,内存利用率不高,每次只能使用50%的内存.
3.标记整理算法
既然复制算法每次只能使用一半的内存,内存使用率不高,那就再继续优化,还是和标记清楚算法一样使用全部区域的内存,不同于标记清除算法的是进行垃圾回收时,确认无需回收的对象,然后将这些对象进行整理后向一端移动:
标记整理算法的优点在于内存使用率更充分,并且不会产生大量内存碎片.
堆(heap)中的回收算法
java堆采用分代搜集来进行垃圾回收.首先明确一点,堆中为什么要进行分代?或者说,java堆为什么要使用分代收集算法来进行垃圾回收?因为据权威统计,80%以上的对象都是朝生夕死,即这些对象随着方法的执行完毕而不再使用,可以被回收,而剩余的20%左右的对象是还需要继续被使用,无法回收的,因此,jdk根据对象的这种特点进行分代收集,一句话概括就是对象的生命周期不同.
所谓分代收集,就是把java堆分为新生代和老年代,老年代采用标记整理算法,而新生代采用复制算法,其中将新生代划分为伊甸园区(eden)和幸存区(survivor)s0以及s1(有的也称之为survivor from和survivor to),默认情况下其比例为8:1:1,而整个新生代和老年代的比例为1:2(即新生代占整个堆区1/3,而老年代占2/3):
至于新生代和老年代的详细工作流程,就不再赘述,网上这种博客太多了.需要注意的是,发生在新生代的gc称之为minor gc或者 young gc,而发生在老年代的gc称之为full gc或者major gc,一般而言,full gc的效率会比minor gc低十倍以上!
读完本篇文章,我相信童鞋们应该对jvm垃圾回收有了一定的了解,下一篇文章,让我们来学习一下jvm篇最后一个知识点,也是最重要的知识点---jvm性能调优,敬请期待!
如果觉得博主写的不错,欢迎关注博主微信公众号,博主会不定期分享技术干货!
本文由博客一文多发平台 openwrite 发布!
上一篇: MySQL视图与索引(基础介绍)
下一篇: 用C++实现:高精度加法