哪些内存需要回收?
什么时候回收?
如何回收?
程序计数器、虚拟机栈、本地方法栈三个区域随线程而生,随线程而灭,栈中的栈针随着方法的进入和退出而有条不紊地执行者出栈和入栈操作。不需过多的考虑回收问题,当方法结束或线程结束时,内存自然就跟着回收了。
Java堆和方法区有显著的不确定性:一个接口的多个实现类需要内存可能不同,一个方法所执行的不同条件分支所需要的内存也可能不同。只有处于运行期间,我们才能知道程序究竟会创建哪些对象,创建多少个对象,这部分内存的分配和回收是动态的。垃圾回收器关注这部分内容。
判断对象是否存活?
1、引用计数算法:
原理:在对象中添加一个引用计数器,每当一个地方引用时,计数器加一;当引用失效时,计数器减一;任何时刻计数器为零的对象就是不可能再被使用的。
优点是原理简单、效率高,很多地方都都使用此算法进行内存管理。但是在主流的Java虚拟机中没有使用,原因是难以解决对象之间相互循环引用的问题。
2、可达性分析算法:(Java使用)
原理:通过一系列的”GC Roots“的根对象作为起始节点集,从这些节点开始根据引用关系向下搜索,搜索过程中走过的路径称为”引用链“,如果某个对象到GC Roots间没有任何引用链相连,证明对象是不可能再被使用的。可回收。
垃圾收集算法:
引用计数式垃圾收集(直接垃圾收集):java未用到,不讨论。
追踪式垃圾收集(间接垃圾收集):
1、标记-清除算法:
原理:首先标记所有需要回收的对象,然后统一回收所有被标记的对象。也可先标记存活对象。
缺点:第一是执行效率不稳定;第二是内存空间碎片化。
2、标记-复制算法:优先使用此算法
原理:“半区复制”,把内存分为大小相等的两块,然后只使用其中的一块,当这一块内存使用完之后将存活的对象复制到另一块上。
优点是简单高效;缺点是会造成空间浪费。
对象存活率较高时就会进行较多的复制操作,效率将会降低。
3、标记-整理算法:
原理:针对老年代对象,标记过程仍然像“标记-清除”一样,后续步骤不是直接对可回收对象进行整理,而是让所有存活的对象都想内存空间一端移动,然后直接清理掉边界以外的内存。
实际堆区划分:
堆划分为young区和old区,young区有S0和S1和E区( Eden与Survivor),所有的对象都是在E区中出生,当E区要满了之后会触发young区的GC,把E区中要回收的对象进行标记,然后把存活对象复制到S0,清除E。第二次对E区和S0区的回收对象标记,然后存活对象复制到S1中,清除S0和E区。此后都是这样交替工作。young区使用的是标记-复制算法。
每次young区GC后存活对象都会年龄加1,当年龄加到15,就会保存到old区。大对象的存储直接在old区,old区满了之后,也会GC,同时也伴随youngGC,所以叫full GC。old区使用标记-整理算法。