一.为什么要有垃圾回收?
内存是有限的,如果一直申请资源,而不去释放资源,可用空间越来越少,就会引起内存泄漏问题。
二.垃圾回收主要回收哪个内存区域?
程序计数器:空间固定,每个线程都有一个程序计数器,跟随线程一起销毁。
栈:主要是局部变量,约定的变量出了作用域,就会被回收。
方法区:类对象,主要设计类加载,会但很少涉及类卸载。
堆:new对象(主要回收空间)
三.如何确定对象是不是垃圾?
1.引用计数
使用额外计数器,记录某个对象有多少个引用指向它。
每次产生一次引用,就把计数器++;销毁一次引用,就把计数器--;如果对象的引用计数为0,则说明没有引用指向该对象,此时改对象就是垃圾可以被回收。
缺陷:(1)多线程中,需要也是引用一个对象时,就会引起线程安全问题。
(2) 如果是小的对象,引用次数过多,引用计数就会造成较大的空间开销。
(3)循环引用问题,即a和b相互引用,如果引用被销毁,此时a和b的引用计数都还是1,都不能被销毁,也不能被引用。
2.可达性分析
以代码中的特殊变量GCRoot为起点,然后以起点出发,能够访问到的变量,就设置为"可达"。
GCRoot:(1)栈里面的局部变量,所有线程的所有栈的所有栈帧的所有局部变量表中的所有变量;(2)常量池中对应的对象;(3)方法区中静态引用类型的成员。
四.具体如何回收垃圾?
1.标记清除
先通过可达性分析确定垃圾,就把垃圾直接释放掉,但是垃圾不是一个连续的内存空间,而new操作时,又要new出来一段连续的内存空间,就引起了内存碎片问题,造成了空间的浪费。
2.复制算法
将内存一分为二,在进行垃圾释放之前,先将要保留的对象复制到另一侧内存空间,然后再将之前的一侧的空间全部释放,就解决了内存碎片问题。但是将内存一分为二就引入了新的问题——空间利用率低。
3.标记整理
类似于链表删除元素,针对内存进行搬运操作,即解决了内存碎片问题,也解决了空间利用率低的问题,但是搬运操作耗时。
4.分代回收
将内存分为新生代和老年代。
将新创建的对象放到伊甸区,如果该对象活过了一轮GC就放入幸存区,进入幸存区的对象再经历了一轮GC后,又通过复制算法进入另一个幸存区中,如果经过多轮GC,该对象仍然存在,就说明该对象一时间不容易被销毁,就把它放入老年代中,进入老年代之后就通过标记整理算法进行处理。