1

jvm 中,程序计数器、虚拟机栈、本地方法栈都是随线程而生随线程而灭,栈帧随着方法的进入和退出做入栈和出栈操作,实现了自动的内存清理,因此,我们的内存垃圾回收主要集中于 堆和方法区中,在程序运行期间,这部分内存的分配和使用都是动态的。


GC算法:

对象存活判断

引用计数法:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。
缺点是无法释放循环引用的对象。如下图:

clipboard.png

根搜索算法:从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
在Java语言中,GC Roots包括:
虚拟机栈中引用的对象。
方法区中类静态属性实体引用的对象。
方法区中常量(final)引用的对象。
本地方法栈中JNI引用的对象。

clipboard.png
可以看到,该算法可以释放循环引用的对象(D和E)。


垃圾收集算法

标记/清除算法:当堆中的有效内存空间(available memory)被耗尽的时候,就会停止整个程序(也被成为stop the world),然后进行两项工作,第一项则是标记,第二项则是清除。

clipboard.png

(1)标记:标记的过程其实就是,遍历所有的GC Roots,然后将所有GC Roots可达的对象标记为存活的对象。
(2)清除:清除的过程将遍历堆中所有的对象,将没有标记的对象全部清除掉。

缺点:1、首先,它的缺点就是效率比较低(递归与全堆对象遍历),而且在进行GC的时候,需要停止应用程序,这会导致用户体验非常差劲
2、第二点主要的缺点,则是这种方式清理出来的空闲内存是不连续的(碎片化),JVM就不得不维持一个内存的空闲列表,这又是一种开销。而且在分配数组对象的时候,寻找连续的内存空间会不太好找。

复制(copying)算法:将内存划分为两个区间,所有动态分配的对象都只能分配在其中一个区间(称为活动区间),而另外一个区间(称为空闲区间)则是空闲的,当有效内存空间耗尽时,JVM将暂停程序运行,开启复制算法GC线程。将活动区间内的存活对象,全部复制到空闲区间,且严格按照内存地址依次排列,与此同时,GC线程将更新存活对象的内存引用地址指向新的内存地址。此时,空闲区间已经与活动区间交换,而垃圾对象现在已经全部留在了原来的活动区间。事实上,在活动区间转换为空间区间的同时,垃圾对象已经被一次性全部回收。

clipboard.png

缺点:1、它浪费了一半的内存。
2、如果对象的存活率很高,我们可以极端一点,假设是100%存活,那么我们需要将所有对象都复制一遍,并将所有引用地址重置一遍。复制这一工作所花费的时间,在对象存活率达到一定程度时,将会变的不可忽视。

标记/整理算法:标记/整理算法与标记/清除算法非常相似,它也是分为两个阶段:标记和整理。

clipboard.png

(1)标记:它的第一个阶段与标记/清除算法是一模一样的,均是遍历GC Roots,然后将存活的对象标记。

(2)整理:移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。因此,第二阶段才称为整理阶段。
优点:标记/整理算法不仅可以弥补标记/清除算法当中,内存区域分散的缺点,也消除了复制算法当中,内存减半的高额代价。
缺点:效率也不高,不仅要标记所有存活对象,还要整理所有存活对象的引用地址。从效率上来说,标记/整理算法要低于复制算法。

总结:1、三个算法都基于根搜索算法去判断一个对象是否应该被回收,而支撑根搜索算法可以正常工作的理论依据,就是语法中变量作用域的相关内容。因此,要想防止内存泄露,最根本的办法就是掌握好变量作用域,
2、在GC线程开启时,或者说GC过程开始时,它们都要暂停应用程序(stop the world)。
3、性能比较
效率:复制算法>标记/整理算法>标记/清除算法(此处的效率只是简单的对比时间复杂度,实际情况不一定如此)。
内存整齐度:复制算法=标记/整理算法>标记/清除算法。
内存利用率:标记/整理算法=标记/清除算法>复制算法。


分代收集算法

GC分代的基本假设:绝大部分对象的生命周期都非常短暂,存活时间短。
GC将对象数据进行分类。主要是两类:年轻代(Young Generation),老年代(Old Generation)。分代收集主要针对这两类的对象进行回收。

年轻代(Young Generation):年轻代含两种结构,伊甸园空间(1个,占80%)和幸存空间(2个,各占10%)。大多数对象会很快的变得不可达,因此,很多对象会在变成年轻代之后就消失,而这个过程我们称之为“ minor GC”。由于存活率低,选用复制算法,一旦发生GC,将10%的幸存区间与另外80%伊甸园空间中存活的对象转移到10%的幸存空间,接下来,将之前90%的内存全部释放。

clipboard.png

年轻代遵循以下规则:
通常刚刚被创建的对象会存放在伊甸园空间。
伊甸园空间执行GC后,将Eden和From活着的对象一次性复制到另一个名为To的Survivor中去,然后清理Eden和From

执行GC多次后,依然存活的对象会被转移至老年代。

老年代(old Generation):对象来自新生代,如上所说部分对象会不可达,而剩下的从年轻代中存活下来,被拷贝至老年代。老年代所占用的空间要比年轻代多。如上,对象在老年代也会消失,而这个过程被称之为“major GC”(或者是 “full GC”).老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清除”或“标记-整理”算法来进行回收。

Permanebt Generation :持久代,或者称为方法区(method area),通常持久代用来保存类常量以及字符串常量。而特别需要注意这个持久代区域不是用来保存从老年代存活下来的对象的。持久代也可以发生GC。同时这个区域的GC会被看待为 major GC.(使用“标记-清除”或“标记-整理”算法)
永久代主要回收两种:常量池中的常量,无用的类信息。
要知道常量的回收是相对简单的,主要是无用的类回收比较麻烦,要注意以下几点:
类的实例已经全部被回收了
ClassLoader已经被回收
类的对象没有被引用

通常情况下,以下两种情况发生的时候,对象会从新生代区域转到年老带区域。
1、在年轻代里的每一个对象,都会有一个年龄,当这些对象的年龄到达一定程度时(年龄就是熬过的GC次数,每次GC如果对象存活下来,则年龄加1),则会被转到年老代,而这个转入年老代的年龄值,一般在JVM中是可以设置的。

2、在年轻代存活对象占用的内存超过10%时,则多余的对象会放入年老代。这种时候,年老代就是新生代的“备用仓库”。

参考文章:
https://www.cnblogs.com/sunfi...
http://www.cnblogs.com/ityouk...
http://blog.csdn.net/u0116690...


Myosotis
89 声望13 粉丝