JVM 垃圾回收
Java的最大特性是内存自动分配和垃圾自动回收,程序员不需要手动去处理这两件事情。
一.概览
垃圾回收主要包含了三部分:
如何标识出垃圾对象
何时进行垃圾回收
如何进行垃圾回收
二.如何标识出垃圾
如何在内存中识别出那些对象是要被回收的垃圾对象,那些是要被留下来有用对象呢?
JVM主要有两种算法来标识对象,1:引用计数算法。2:GcRoot可达性分析。
1.引用计数算法
描述:引用计数算法是指给对象中添加一个引用计数器,每当有个地方引用它的时候,该对象的计数器的值就+1,如果取消对该对象引用时,该引用计数器-1,一旦该对象的引用计数器为0的时候,那么此对象就可以被标识为可回收对象。
优点:引用计数算法实现简单,而且判断效率高。
缺点:很难解决对象间相互循环引用的问题。
示例:如对象objA和objB都有字段instance,赋值objA.instance = objeB,objB.instance = objA,除了上面的两个引用再无其他引用,实际上这两个对象已经不可能再被访问,但是两个对象互相引用,导致他们的引用计数器不为0,采用引用计数算法就无法通知GC收集器进行回收。下面的实例只能说明目前的垃圾回收器未使用引用计数算法。
2.可达性分析
描述:可达性分析是通过一系列称为`GC Roots`的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路线称为引用链(Reference Chain),当一份对象到GC Root没有任何引用链存在,则证明此对象不可用。
可作为GC Roots的对象如下:
虚拟机栈(栈中的本地变量表中)中引用的对象。
方法区中类静态属性引用的对象。
方法区中常量引用的对象。
本地方法栈中JNI(即一般说的Native方法)引用的对象。
优点:可以有效解决引用计数算法中对象间相互引用的问题。
缺点:难度稍大,复杂度较高。
如图:
三.何时进行垃圾回收
cpu空闲时进行垃圾回收。
堆满的时候进行垃圾回收。
程序显式调用System.gc();是进行垃圾回收。
四.如何进行垃圾回收
此处主要是将垃圾回收算法,包含1:标记-清除,2:标记-整理,3:复制,4:分代
1.标记-清除
标记-清除算法包含两部分,一是标记,二是清除,首先是先标记出所需要回收的对象,然后回收所标记的对象。
缺点:1:效率问题,标记和清除效率都不高;2:空间问题,容易产生垃圾碎片,空间碎片太多容易导致分配大对象时,无法找到足够大的连续的内存空间,不得已触发另一次垃圾回收动作。(碎片指的不连续的一小块一小块内存)。
2.标记-整理
标记整理算法同样分为两步,一是标记,二是整理,和标记-清除算法的区别是:整理不是直接对对象进行清理,而是让所有存活的对象都移向一端,然后直接清理掉端边界以外的内存对象。
优点:可以很好的避免垃圾碎片产生,也可以不想复制算法那样浪费掉一半的空间
3.复制算法
复制算法是指将内存分为大小相等两块,每次只是用一块,当一块内存用完后,将存活的对象移动到另一块,然后一次将其清理掉。
优点:简单、高效、不存在垃圾碎片。
缺点:每次只是用内存的一半,造成了内存浪费。
4.分代算法
分带算法其实是通过以上算法优化后的算法,是按照对象的生命周期分成响应的部分,一般分为新生代和老年代,新生代中的对象往往是朝生夕死的,只有少量对象可以存活,而老年代往往是相反的,存活率高。通常新生代由复制算法实现(新生代存活对象少,复制成本低),老年代由标记-清除或标记-整理算法时间。
现在较好的商业虚拟机中都采用了分代回收算法。
分代回收算法中的新生代又可以分为 Eden区,To survivor、From survivor,三者大小占比为8:1:1。
工作流程:
1:对象分配首先在Eden区中分配。
2:如果Eden区满了后会进行一次minorGC,将存活对象放入到To survivor区中,清理掉Eden区。
3:对象分配继续在Eden中分配,如果Eden区中又满了,那么将Eden区中存活对象和To survivor区中的存活对象复制到from survivor区中,清理掉Eden区和To survivor区。
4:继续执行1->2->3流程
5:说明:Java对象头中包含了GC回收年龄,如果此年龄大于15的话就将此对象移到老年代。
五.回收方法区
此部分是重读<<深入理解Java虚拟机>>这本书的时候拾漏的。
一般情况垃圾回收是发生在堆中的,堆中新生代一般每次可以回收70%到95%的对象。方法区中也是会进行垃圾回收的,但是方法区中垃圾回收性价比一般很低。
在大量使用反射、动态代理、CGLIB等再运行时生成大量动态类(或代理类)的情况下,必须具备卸载类的功能,以防止永久代溢出。
永久代(元数据区)的垃圾回收主要回收两部分内容:废弃常量和无用的类。
废弃常量:如果有一个字符串"abc"已经进入了常量池,但是当前系统没有任何一个String对象引用常量池中的"abc"常量,如果发生垃圾回收时,这个"abc"常量会被系统清理出常量池。
无用的类:判断一个类是无用的类比较苛刻,需满足如下3个条件:
该类所在的实例都被回收,也就是Java堆中不存在该类的任何实例。
加载该类的类加载器(ClassLoader)被回收。
该类对应的java.lang.Class对象没有在任何地方引用。
版权声明: 本文为 InfoQ 作者【少林寺三毛】的原创文章。
原文链接:【http://xie.infoq.cn/article/02ec90e1b28dde71ed8c15836】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论