目录
1.判定垃圾
1.引用计数
2.可达性分析
2.清理垃圾
1.标记清除
2.复制算法
3.标记整理
4.分代回收
上文讲述的Java运行时内存划分,对于程序计数器,虚拟机栈,本地方法栈来说,生命周期是和线程有关的,随着线程而生,随线程而灭,当方法结束或者线程结束时,它们的内存就自然跟着回收了.这里讨论的垃圾回收是和堆,方法区这两个区域有关的
堆中存放着几乎所有的实例对象,垃圾回收器在对堆进行垃圾回收前,还要判断哪些对象还存活,哪些已经"死亡了".垃圾回收就是帮我们将不适用的内存给自动释放了,如果内存一直占着不用,又不是释放,就会导致剩余的内存空间越来愈少,进一步导致后续申请内存操作失败,服务器是一直运行的,最害怕内存泄漏的问题!
GC 是以对象为单位进行垃圾回收的,正在使用的对象和一部分被使用,一部分未被使用的对象不进行回收,回收的是不被使用的对象,不会回收半个对象.
GC的工作过程
被使用,没有被引用指向的对象,就是垃圾.可回收
如何进行对象是否有引用的判定?
不是Java中的做法,python/PHP的方式
给每个对象都分配一个计数器(整数),每次创建一个引用指向该对象,计数器就+1,每次该引用被销毁,计数器就-1
{
Test t = new Test();//Test对象引用计数1
Test t2 = t;//t2指向t,引用计数2
Test t3 = t;//t3指向t,引用计数3
}
当大括号结束,上述三个引用出作用域失效了,引用计数变为0
此时Test对象就是垃圾了
这个方法简单高效,但是内存空间利用率低,每个对象都要分配计数器,如果对象太多了,那么耗费的内存就很多了
还有个缺点,存在循环引用的问题
class{
Test t = null;
}
Test a = new Test();//1号对象引用计数为1
Test b = new Test();//2号对象引用计数为1
a.t = b;//1号对象引用计数为2
b.t = a;//2号对象引用计数为2
此时,如果ab引用都销毁,1号2号对象的引用计数都-1,但是不为0,那么就不能进行垃圾回收,但是其实这两个对象,已经没有办法访问到了
Java的做法,Java中的对象,都是通过引用来指向并且访问的,经常是一个引用指向一个对象,这个对象中的成员又指向别的对象,例如
class TreeNode{
int value;
TreeNode left;
TreeNode right;
}
java中的所有对象,都是通过类似上述的关系,通过链式/树形结构串起来的
可达性分析:是把所有的对象被组织的结构视为树,从根节点出发,遍历树,所有能被访问到的树,就标记为"可达",不能被访问的就是不可达,JVM将不可达的对象回收
可达性分析需要进行类似树的遍历的操作,相比于引用计数来说慢一些,但是可达性分析并不是一直进行的,只需要隔一段时间,分析一次,回收一次就可以了
进行可达性分析的起点,叫GCroots,栈上的局部变量,常量池中的对象,静态成员是变量,一个代码中有很多这样的起点,把每个起点都往下遍历一遍,就完成了一次扫瞄过程
第二步,如何清理垃圾?
有三种基本做法
先标记出需要回收的对象,在标记完成之后统一回收所有被标记的对象
缺点:带来了内存碎片问题,这些被释放的内存是零散的,细碎的空间,申请时大内存会申请失败