垃圾回收机制(GC)是java常重要特性之一。它让开发者无需关注内存的创建和释放,而是通过GC自动回收垃圾(无用对象)。
哪些内存需要回收
java堆和方法区是垃圾回收的主要内存区域,程序计数器、虚拟机栈、本地方法栈这几个内存区域是现成私有的,线程结束时内存自然也就回收了。
如何判断对象可回收?
在java堆里存放着几乎所有的对象实例,垃圾收集器在进行垃圾回收之前第一件事情就是判断哪些对象还"活着",哪些对象已"死去"(不会再被使用的对象);
引数记数法
引数记数法就是给对象添加一个引数计数器,每当有其他地方引用对象时,计数器就加1;当引用失效时,计数器就减1,如果计数器任何时刻都为0,那么对象不可能在被使用,垃圾收集器就可以进行回收了。引数记数法简单高效,微软公司的COM技术,python语言,游戏脚本语言Squirrel都是使用了引数记数法进行内存管理。但是主流java虚拟机没有使用引述记数法进行内存管理,原因主要是他很难解决对象之间互相引用的的问题。
可达性分析算法
java虚拟机是通过可达性分析算法来判定对象是否存活的。可达性分析算法的主要思想是通过一系列的称为"GC Roots"的对象作为起始点从这些起点开始向下搜索,搜索走过的路径称为引用链,当一个对象到"GC Roots"没有任何引用链相连时("GC Roots"到此对象不可达),则证明此对象是不可用,可回收的。
在java语言中,可作为"GC Roots"的对象包括以下几种
- 虚拟机栈()中引用的对象
- 方法区中类静态变量引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI引用的对象
垃圾收集算法
简单介绍下几种垃圾收集算法
标记-清除算法
最简单的垃圾收集算法是“标记-清除”算法,算法分为“标记”和“清除”两个阶段:首先要标记出需要回收的对象,在完成标记后统一回收所有被标记的对象。标记过程就是判断哪些对象需要回收。
标记-清除算法有两个不足之处: 一个是效率问题,标记-清除的效率都不高; 另一个空间问题,标记-清除之后会产生大量不连续的内存碎片,内存空间碎片太多可能会导致java虚拟机在分配较大对象时,无法找到足够的连续内存而触发一次垃圾收集动作。复制算法
复制算法是为了解决标记-清除算法的效率问题而产生的。它是如何解决效率问题的呢?
复制算法将内存分为两块大小相等的两块,每次只使用其中一块。当这一块的内存用完了,将存活的对象复制到另一块上,然后将使用过的一块内存全部清理掉。这样每次都是对整个半区进行垃圾回收,内存分配时就不需要担心内存碎片的问题了。复制算法实现简单,运行高效。代价就是内存缩小为原来的一半。
复制算法主要用来回收新生代,IBM的专家研究过,新生代的中的对象98%都是“朝生夕死”的,并不需要按照1:1的比例来划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次只使用Eden和其中一块Survivor空间。当进行垃圾回收时将Eden和Survivor中还存活的对象复制到另一块Survivor空间上,最后清理掉Eden和使用过的Survivor空间。Eden空间和两块Survivor空间之间的比例时8:1:1,新生代可用空间为90%(80% + 10%),只浪费了10%内存空间。当存活对象多于10%时,Survivor空间不够用,这些对象将通过直接进入老年代。
标记整理算法
老年代是通过标记-整理算法来进行垃圾回收的。标记过程与标记-清除算法一样,清除过程不是直接清理可回收对象,而是让所有的对象都向一端移动,然后直接清理掉边界以外的内存。
分代收集算法
现代商业虚拟机的垃圾收集都采用“分代收集”算法,分代收集算法根据对象存活周期的不同将java堆内存分为新生代和老年代。新生代每次垃圾收集时大量对象死去,只有少量对象存活,那就使用复制算法。老年代中的对象存活率高,没有额外内存空间进行担保,那就使用标记清除算法或者标记整理算法。
垃圾收集器
未完。。。
参考资料
《深入理解java虚拟机》