什么是垃圾回收
垃圾回收(Garbage Collection,GC),主要是针对堆内存中无效的对象进行回收,释放空间,防止内存泄漏。
垃圾回收,需要关注两点:
第一点:如何判断为垃圾对象;
第二点:如何回收,指的就是回收算法;
查看垃圾回收信息,在 Run Configurations 的 Arguments 加上如下参数就可看到回收日志。
-verbose:gc -XX:+PrintGCDetails
第一部分,如何判定垃圾对象
1.引用计数法
在对象(头)中添加一个引用计数器,当有地方引用这个对象的时候,引用计数器的值就 +1,当引用失效的时候,计数器的值就 -1。
所以当对象的计数器为 0 时,该对象是可回收的。
但此算法有一个缺陷,当两个或多个对象内互相引用时,这些对象无法被回收。
public class Test1 {
public Object obj;
public static void main(String[] args) {
Test1 a = new Test1();
Test1 b = new Test1();
a.obj = b;
b.obj = a;
a = null;
b = null;
System.gc();
}
}
a 指向 X011 地址的对象里面有个 obj 成员变量,指向 X012 的对象;
b 指向 X012 地址的对象里面有个 obj 成员变量,指向 X011 的对象;
所以这两个堆里边的两个对象互相引用了,此时两个对象的计数器都是 2。
当 a 和 b 都置为空时,上图 1 和 2 的引用就断了,此时计数器都是 1。
由于两个对象一直互相引用,所以永远不会被回收。
2.可达性分析法
可达性分析法是指,通过一些被称为根节点(GC Roots) 作为起点,从这些根节点开始向下搜索,搜索走过的路径被称为引用链(Reference Chain)。当一个对象到 GC Roots 没有任何引用链相连时(即从 GC Roots 节点到该节点不可达),则证明该对象是不可用的。
a 作为根节点,一层层往下找,能找到引用的对象,说明对象是存活的,这条查找路径称之为引用链(上图a->X014 的路径称为引用链)。而 X015 没有被任何根节点引用(不可达),所以该对象可以被回收。
可达性分析法解决了引用计数法中无法解决循环引用的问题,只要对象没有直接或间接相连的 GCRoot,这该对象就被认为可回收。
哪些可以作为GCRoot
1.虚拟机栈
2.方法区的类(静态)属性所引用的对象
3.方法区中常量所引用的对象
4.本地方法栈中引用的对象
第二部分,如何回收
1.回收策略
①标记-清除算法
将可回收的对象进行标记,然后清除。
此算法有两大问题,一是效率问题,二是空间问题。
被标记的对象被清除的时候,堆内存空间产生很多不连续的空间。
当要在堆内存开辟一个很大的空间的时候,需要不断寻址找到可以足够大的空间,这会影响到性能。如果没有找到足够大的空间,那么就需要再次触发垃圾回收,再进行回收,这也会影响到性能。
②复制算法
将内存空间分为上下两部分,首先将对象存放在上方区域,下方区域内无任何对象。
垃圾回收器将标记的对象回收,然后将存活的对象有序地复制到下方区域,上方区域就是空的。
接着有创建新的对象的时候,就会在下方区域开辟空间。
然后再发生垃圾回收的时候,就会将存活的对象复制到上方,一直这样循环使用。
复制算法解决了标记清除算法中空间不连续的问题。但是复制算法有一个缺点就是空间利用率不高,因为它需要将空间一分为二。
如果整个堆内存用这个算法回收的话,那么就会有 50% 的空间是空闲的,空间利用率不高。所以此算法中用于 Survivor 区。
Survivor 占用 20% 的空间,分为两块,也只是浪费 10% 的空间。
③标记-整理算法
此算法其实是标记-整理-清除,常用于需回收的内存不多的空间,例如老年代。
此算法比 ① 算法多了整理,该算法会将不需要回收的内存移向内存的一段,需要回收的移向另一端。
需要回收的移向右侧,不需要回收的移向左侧,这就是整理的过程,清除只需要清除右侧空间就可以了,大大提升了回收效率。
此算法解决了内存碎片的问题,也规避了复制算法只能使用一半空间区域的弊端,但是由于此算法需要对内存中的所有对象进行移动整理,是很耗费时间的。
④分代收集算法
此算法是标记整理算法和复制算法的结合。
我们都知道内存分为新生代和老年代,针对新生代和老年代不同的特点,就采用分代收集算法。
分代收集算法根据内存的分代选择不同的垃圾回收算法。
也就是说,它会根根据新生代这种内存回收率较高的区域,选择复制算法;对于老年代或者内存回收率较低的区域,则选择标记整理算法。
2.垃圾收集器
①Serial
最基本,发展最悠久
单线程垃圾收集器
桌面应用
②Parnew
Serial 是单线程的,它是多线程的
③Parallel
复制算法(新生代收集器)
多线程收集器
达到可控制的吞吐量
-XX:MaxGCPauseMillis 垃圾收集器最大停顿时间
-XX:GCTimeRatio 吞吐量大小 (0,100)
吞吐量:CPU用于运行用户代码的时间 与 CPU 消耗的总时间的比值。
吞吐量 = 执行用户代码时间 /( 执行用户代码的时间 + 垃圾回收所占用的时间)
④Cms
Concurrent Mark Sweep
工作过程:
1.初始标记
2.并发标记
3.重新标记
4.并发处理
优点:并发收集、低停顿
缺点:占用大量的CPU资源、无法处理浮动垃圾、出现 Concurrent Mode Failure、空间碎片
⑤G1
优势:并行于并发、分代收集、空间整合、可预测的停顿
工作过程:
1.初始标记
2.并发标记
3.最终标记
4.筛选回收