**
垃圾收集器与内存分配策略
**
最近在看《深入理解Java虚拟机》这本书,就对书的内容做了一些整理。
————————————————————————————————————————————
程序计数器、虚拟机栈、本地方法栈随线程而生,随线程而灭。栈中的栈帧随着方法的进入和退出有条不紊的执行入栈和出栈,每一栈帧分配多少内存几乎是在类结构确定下来时就是已知的,这几个区域不需要过多的考虑内存回收的问题,因为方法结束或线程结束时,内存就自然跟随着回收了。而Java堆和方法去就不一样,它们在只有在具体运行时才知道具体徐娅多少内存,这部分内存的分配和回收都是动态的,垃圾收集器所关注的也是这部分内存。
垃圾收集器在对堆进行回收时,第一件事就是要确定这些对象中哪些还存活着,判断的方法如下:
1)引用计数法:给对象添加一个引用计数器,当每有一个地方引用它时,就将计数器加一;当引用失效时,计数器值就减一。
优点:实现简单、判定效率高。
缺点:很难解决对象之间相互循环引用的问题。(因此虚拟机用的并不是这种算法)
2)可达性分析算法:这个算法的基本思想就是通过一系列称为CG roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC roots没有任何引用链的时候,则证明该对象是不可用的。
Java语言中,可作为CG roots的有:
1)虚拟机栈中(栈帧中的本地变量表)引用的对象
2)方法区中静态属性引用的对象
3)方法区中常量引用的对象
4)本地方法栈中JNI(一般说的native方法)引用的对象
**
再谈引用:
**
**
垃圾收集算法:
**
1)标记-清除算法:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。判断哪些对象需要被标记使用的是可达性分析算法或其它。该算法是一个基础算法是因为后续的算法都是在它的基础上改进的,它的不足为:1)效率问题,标记和清除这两个过程的效率不高。2)空间问题,标记和清除会产生大量的不连续的内存碎片,空间碎片太多可能会导致以后程序的运行过程中需要分配较大的对象的时候,无法找到足够的连续内存而不得不触发另一次垃圾收集动作。
2)复制算法:是为了解决标记-清除算法的效率问题(有内存碎片化导致的GC频繁)。
将内存按照容量划分为容量大小相等的两块,在任意的时间点,每次动态分配对象只使用其中的一块。另一个块空闲。当这一块的内存用完时,JVM将会运行GC线程,然后将仍然存活的对象全部复制到空闲块中,然后将这个块的内存空间一次性清理掉。即每次都是对半个块进行内存回收。
优点:内存分配时不用考虑内存碎片等复杂情况,只需移动堆顶的指针,按顺序分配内存即可,实现简单,运行高效。
缺点:1)将内存缩小为原来的一半
2)如果对象的存活率很高,我们需要将所有对象都复制一遍,并将所有引用地址重置一遍,这样,复制的时间就会变得很长。
现在的商业虚拟机都用这种方法来回收新生代,因为新生代中98%的对象都是朝生夕死,并且此时内存空间的划分也不是平均的,一般会分成一个较大的Eden空间和两个较小的survivor空间,每次使用Eden和其中一块survivor。当回收时,将Eden和survivor中还存活的对象一次性复制到另一块survivor中,最后清理掉刚刚使用过的Eden和survivor空间。
为什么要设置两块survivor区?
每次分配内存使用的都是Eden中的空间,当Eden中的空间满时,会进行GC线程将Eden中存活的对象复制到survivor S0中。然后继续分配内存,使用的仍然是Eden区,当Eden区再次满时,S0中也有对象,且这两部分对象所占的内存是不连续的,就导致了内存的碎片化。
使用两个survivor,当Eden再次满时,将S0和Eden中的对象一起复制到S1中去,复制算法保证了S1中的内存的连续性。
参考文章:http://blog.csdn.net/antony9118/article/details/51425581
3)标记整理算法:老年代使用的方法。解决复制算法在对象存活率较高时效率较低和50%的空间浪费的问题。
标记的过程任然与标记-清除算法一样,但是后续步骤不是直接对可回收对象的清理,而是让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存。
4)分代收集算法:虚拟机一般都采用分代收集算法,根据对象存活周期的不同将内存划分成不同的区域,一般分为新生代和老年代。根据不同年代的特定使用不同的算法。