以前在一次技术分享会上,听到一个梗。在食堂吃饭,吃完并且收拾餐盘的,就是C++开发人员;吃完直接就走的,就是Java开发人员。
众所周知,Java虚拟机底层维护了一种垃圾回收机制,开发人员无需关注垃圾回收。但是,这并不意味着垃圾回收对于开发人员不重要,恰恰相反,反而是最重要。当出现垃圾回收引起的性能问题,才不会变得束手无策。
搞清楚垃圾回收需要弄清楚以下几个点:
1、什么是垃圾回收?
2、什么是垃圾?
3、垃圾回收的算法。
4、Java堆的内存模型和回收机制。
接下来依次对几种问题进行分析,帮助大家更全面的理解垃圾回收机制。
第一、什么是垃圾回收?
顾名思义,垃圾回收即回收内存中的垃圾,对内存中的已经死亡或者长时间不再使用的对象或变量进行清除和回收。
第二、什么是垃圾?
确定某个对象是否被引用,一般采用以下两种算法:
1、引用计数法:每个对象对应着一个引用计数器,每被引用一次,计数器加1,引用失效时,计数器减1。当计数器一段时间内保持为0,则认为该对象可以被回收了(JDK1.2之前使用的)。
该算法无法解决相互引用的情况,会存在无法回收的情况。
2、可达性分析算法:该算法认为所有的引用都可以使用一个引用链来进行表示。如下图所示,从下往上,每个节点到root节点之间,不存在任何一个引用链时,则证明此对象不可用了。
可以作为GC Roots的对象包括以下几种:
虚拟机栈中的引用对象、方法区中的静态引用对象、方法区中的常量引用的对象、本地方法栈中本地方法引用的对象
第三、垃圾回收的算法
1、标记清除算法:首先标记出所有需要回收的对象,在标记完成之后,完成所有标记对象的回收。
缺点:会产生大量连续的内存碎片,无法分配较大对象的内存。
2、标记整理算法:跟标记清除类似,但是不是直接对标记对象进行回收,而是让所有存活的对象都向一端移动,直接清理边界以外的内存。
3、复制算法:需要将内存划分为两块大小相同的区域,每次只使用其中一块。当某一块内存使用完了之后,会将存活的对象复制到另外一块内存中去,然后再把使用过内存空间直接清理掉。
效率较高,无内存碎片,但是使用内存缩小了一半。
4、分代回收:根据对象的存活周期不同,将内存分为几块。java堆一般分为新生代和年老代。根据各个年代的特点采用合适的算法进行回收。
新生代:每次回收都是大量死去、少量存活的对象,复制算法效率较高。
年老代:对象的存活周期都是比较长的,可以使用标记整理算法。
第四、Java堆的内存模型和回收机制
Java中最大的一块内存是堆,也是垃圾回收最主要的部分。
JVM中,堆被划分为:新生代和年老代。新生代又被划分为Eden、From Survivor、To Survivor。
新生代和年老代的比例为1:2;Eden、From Survivor和To Survivor的比例为8:1:1,这些比例系数可以通过对应的参数进行调整。
这里面需要深入思考两个问题:
1、为啥需要survivor区
假如没有survivor区,发生minorGC时,新生代存活的对象就会被送往年老代。这样会很快造成年老代被存满,会增加fullGC发生的次数。jvm规定,在survivor区存活16次的对象才会被送往年老代。
2、为啥需要两个survivor区
两个survivor区主要是为了解决内存碎片化,可以使用复制算法。每次触发minor GC时,会将eden区和其中一个survivor区中存活的对象复制到另一个survivor区,可以始终保持一个survivor处于空闲态。