在讲垃圾回收之前,我们应该先搞清楚几个问题:
1.哪些内存需要回收
2.什么时候回收
3.如何回收
前面已经提到java内存运行时区域的各个部分,其中程序计数器、虚拟机栈、本地方法栈这个3个区域随着线程而生,随着线程而灭。
栈中是有栈帧的,栈帧随着方法进入或退出栈,每一个栈帧分配多少内存其实在类结构确定下来的时候就是已知的-因为归根结底操作的都是基本数据类型,基本数据类型的大小都是已知的。
哪些内存需要回收
堆里存放着几乎java中所有的对象实例,垃圾收集器在对堆进行回收之前,首先要做的就是确定哪些对象还 “活着”
什么时候回收
当一个对象实例 “死亡”时,即没有任何引用的对象实例,该对象会被回收。
判断一个类是不是无用的类,必须同时满足以下三点,该类的内存才会被回收
1.该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例。
2.加载该类的ClassLoader已经被回收。
3.该类对应的lava.lang.Class对象没有在任何地方被引用,无法再任何地方通过反射访问该类的方法。
如何回收
下面介绍两种算法
1.引用计数算法
很多教科书上判断对象是否存活的算法都是这样的:给对象添加一个引用计数器,每当有一个地方引用他时,计数器的值就+1,引用失效时计数器的值就-1,任何时刻计数器的值等于0,就说明该对象不在被引用。
注:这种算法存在一些问题,他很难解决对象之间相互循环引用的问题。
目前的虚拟机情况
package text1;
public class ReferenceCountingGC {
public Object instance=null;
private static final int _1MB =1024*1024;
private byte[] bigSize =new byte[2*_1MB];
public static void testGC() {
ReferenceCountingGC objA=new ReferenceCountingGC();
ReferenceCountingGC objB=new ReferenceCountingGC();
objA.instance=objB;
objB.instance=objA;
objA=null;
objB=null;
//假设在这发生GC,objA和objB能否被回收?
System.gc();
}
}
上面的代码中,虚拟机并没有因为objA和objB两个对象相互引用而不回收他们。这就说明了虚拟机并不是通过技术算法判断对象是否存活。
这里介绍第二种算法
2.可达性分析算法
现在的主流算法都是可达性分析算法来判断对象是否存活
图解:(图片来源:https://www.jianshu.com/p/8f5fa8288d9b)
这种算法的思路是通过一系列的称为GC Roots的对象作为起始点,从这些节点开始往下搜索,走过的路径称为引用链,当一个对象没有任何引用链时,说明这个对象是不可用的
在java语言中,可以作为GC Root的对象包括以下几种:
1.虚拟机栈中引用的对象
2.方法区中类静态属性引用的对象
3.方法区中常量引用的对象
4.本地方法栈中JNI引用的对象
再谈引用
无论哪种算法,判定对象是否存活都与“引用”有关。
引用也有强弱之分,分别为强引用,软引用,弱引用,虚引用。强度依次递减。
1.强引用就是我们平时说的引用,只要存在强引用,垃圾回收器永远不会回收这个对象。
2.软引用:在将要发生内存溢出之前,将会把这些对象列进回收范围之中进行第二次回收,如果回收之后还没有足够内存,才会抛出内存溢出异常。
3.弱引用:弱引用比软引用更弱一些,他只能存活到下次垃圾回收之前,不管内存够不够,都会被回收。
4.虚引用:他是最弱的一种引用关系,一个对象是否有虚引用,完全不影响对其生存时间构成影响,只是在这个对象被回收的时候收到一个系统提示。