垃圾回收的基本思想就是判断一个对象是否可触及性,说白了就是判断一个对象是否可以访问,如果对象对引用了,说明对象正在被使用,如果发现对象没有被引用,说明对象已经不再使用了,不再使用的对象可以被回收,但是不一定立马被回收,取决于GC垃圾回收的算法。
判断对象的可触及性。
1.可以触及的:从根节点开始,可以到达这个对象,说明这个对象还在使用。
2.可复活的:对象的所有引用都被释放,但是对象可能在finalize()方法复活了。
3.不可触及的:对象的finalize()被调用,但是没有复活,那就彻底挂了,进入不可触及的状态。
不可触及的对象能复活吗?答案是不能,为啥,因为finalize()方法只会调用一次。
上面的三种情况只有不可触及的对象才可以被收回。
1.1. 对象的复活
对象的复活跟人的生老病死一样,在你临死的时候还有可能回光返照一次,如果成功了就复活了。所以想复活只抓住这一次机会,程序的回光返照就是finalize()方法,因为finalize()只会执行一次。
这里给出例子,演示这种情况,看下面的例子:
public class CanObj {
public static CanObj canObj;
@Override
public String toString() {
return " i am canObj";
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize");
this.canObj=this;
}
public static void main(String[] args) throws Exception {
canObj=new CanObj();
canObj=null;
System.gc();
Thread.sleep(1000);
if (canObj==null) {
System.out.println("canObj null ");
}else {
System.out.println("canObj is not null");
}
System.out.println("第二次GC");
canObj=null;
System.gc();
Thread.sleep(1000);
if (canObj==null) {
System.out.println("canObj null ");
}else {
System.out.println("canObj is not null");
}
}
}
运行以上代码,输出如下:
finalize
canObj is not null
第二次GC
canObj null
说明确实触发finalize()方法了,而且只调用一次。
1.1.1. finalize说明
1.finalize()方法推荐不要使用
2.finalize()可能复活对象,可能发生引用外泄。
3.finalize()是系统调用的。所以调用时间是不确定的。所以资源的释放最好放在try catch finally中。
1.2. 引用和强、软、弱、和虚强度
java提供了四个级别的引用分别是强引用、软引用、弱引用和虚引用,强引用我们平时使用的就是比如new B()就是个强引用,其他的几种引用都可在在java.lang.ref.Reference找到他们的影子,如图显示了3中引用类型对应的类图关系,开发人员直接使用它们。
1.2.1. 强引用
强引用就是程序中一般的引用类型,强引用对象是可触及的,不会被回收,其他的几种就是在一定情况下是被回收的。
下面是一个强引用的例子:
StringBuffer str=new StringBuffer("hello shareniu");
上面的代码是在函数内可以运行的,那么局部变量str被分配在栈上,对象StringBuffer分配在堆上,局部变量str指向StringBuffer的堆空间,通过str可以操作实例,那么str就是StringBuffer实例的强引用如下图所示:
此时,在运行复制语句:
StringBuffer str1=str;
那么,str所指向的对象也将被str1指向,同事局部变量表也会存放str1的变量如下图所示:
System.out.println(str==str1);
相等的==比较的是操作数指向的堆空间的地址是否相等。
强引用特点如下:
1.强引用可以直接访问目标对象。
2.强引用对象任何时候都不会被系统回收,虚拟机抛出OOM异常也不会回收强引用对象。
3.强引用对象可能造成内存泄漏。
1.2.2. 软引用--可以被回收的引用
一个对象持有软引用,那么当堆空间不足的时候,就会回收这块区域。软引用实现类java.lang.ref.SoftReference<T>
下面演示软引用在系统堆内存不足的时候被回收。
public class SoftRef {
public static class User{
private int id;
private String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + "]";
}
}
public static void main(String[] args) {
User user=new User(1, "shareniu");
SoftReference<User> usSoftReference=new SoftReference<SoftRef.User>(user);
user=null;
System.out.println(usSoftReference.get());
System.gc();
System.out.println("after gc");
System.out.println(usSoftReference.get());
byte []b=new byte[7*925*1024];
System.gc();
System.out.println(usSoftReference.get());
}
}
配置参数-Xmx10m 分配10M内存所以堆空间内存不足的时候,软引用被回收。
程序输出如下:
User [id=1, name=shareniu](第一次从软引用获取到值)
after gc
User [id=1, name=shareniu](GC没有清除软引用,因为堆空间还有)
null(被清除了,因为堆空间慢了)
实验得出结论:GC不一定会回收软引用对象,但是内存不足的时候软引用会被回收,所以软引用对象不会OOM异常。
软引用的时候可以构造一个队列,当软引用对象被回收的时候,就会加入引用队列,通过对了可以追踪对象的回收情况。(在下面的通用例子中会降到)
1.2.3. 弱引用--发现即回收
在系统GC的时候,只要发现弱引用-不管系统堆内存是否使用,都会将对象进行回收,但是垃圾回收器的线程优先级比较低,所以不一定很快就能发现持有的弱引用对象。所以弱引用-可能存在比较长的时间。一旦一个弱引用-对象被回收,变回加入到注册的引用队列中 弱引用-具体的实现类java.lang.ref.WeakReference<T>
下面的例子显示弱引用的特点:
public class WakRef {
public static class User{
private int id;
private String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + "]";
}
}
public static void main(String[] args) {
User user=new User(1, "shareniu");
WeakReference<User> weakReference=new WeakReference<WakRef.User>(user);
user=null;
System.out.println(weakReference.get());
System.gc();
System.out.println("after gc");
System.out.println(weakReference.get());
}
}
程序输出如下:
User [id=1, name=shareniu]
after gc
null
可以看出,在GC之后,弱引用立即被清除了。
弱引用的时候可以构造一个队列,当弱引用对象被回收的时候,就会加入引用队列,通过对了可以追踪对象的回收情况。(在下面的通用例子中会降到)
1.2.4. 虚引用--对象回收追踪
虚引用是所有引用类型中最弱的一个,一个持有虚引用对象。和没有引用基本一样,随时都有可能被回收,当试图使用虚引用的get()获取的时候,总会失败报错,不能获取到。虚引用必须和引用队列一起使用,它的作用就是跟踪垃圾回收的过程。
当垃圾回收器回收一个对象的时候,如果发现他是虚引用,会立马讲这个虚引用的对象加入引用队列,通知应用程序对象的回收情况。所以我们在这里看一下队列如何使用。(除了强引用不能使用队列,其他的三个都可以使用,虚引用是必须使用队列)
下面给出一个实例,使用虚引用跟踪一个可以复活的对象的回收。
public class TraceObj {
@Override
public String toString() {
return "TraceObj ";
}
public static TraceObj obj;
//定义一个队列,对象回收时候会进入这个队列
static ReferenceQueue<TraceObj> queue=null;
public static class CheckRefQuene extends Thread{
@Override
public void run() {
while (true) {
if (queue!=null) {
PhantomReference<TraceObj> phantomReference=null;
try {
phantomReference=(PhantomReference<TraceObj>) queue.remove();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (phantomReference!=null) {
System.out.println(" delete "+phantomReference);
}
}
}
}
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("复活一个对象");
//复活一个对象 finalize只会执行一次
obj=this;
}
public static void main(String[] args) throws Exception {
Thread thread=new CheckRefQuene();
thread.setDaemon(true);
thread.start();
queue=new ReferenceQueue<TraceObj>();
obj=new TraceObj();
PhantomReference<TraceObj> phantomReference=new PhantomReference<TraceObj>(obj, queue);
obj=null;
System.gc();
Thread.sleep(1000);
if (obj==null) {
System.out.println("null ");
}else {
System.out.println("obj is not null ");
}
System.out.println("第二次gc");
obj=null;
System.gc();
Thread.sleep(1000);
if (obj==null) {
System.out.println("null ");
}else {
System.out.println("obj is not null ");
}
}
}
程序的输出如下:
复活一个对象
obj is not null
第二次gc
delete java.lang.ref.PhantomReference@1d86fd3 (删除的对象确实进入队列了)
null
虚引用对象可以跟踪对象的回收时间,所以,可以将一些资源释放操作放在虚引用中执行和记录。
1.2.5. 比较
软、弱、和虚 引用都可以放置到队列中,强引用不需要,虚引用必须要使用队列。
软引用可以放一些缓存的数据,当内存不足的时候被回收
软引用在设备内存比较少的时候特别有用,比如android系统。
一个android应用如果设计到通过网络获取图片,为了让系统更快的运行和更节省流量我们可以将已经下载下来的图片缓存起来,当第二次浏览到该图片时就可以从缓存中拿。
缓存的方式有:一是放在系统内存中这样效率最高,二是把文件写到外部存储器上。但是就目前而言android系统的内存是非常的有限的不可能像PC机那样配置那么高的内存,而且外部存储器的容量也是有限的。
如何我们用SoftReference的方式存储在内存中是一中很好的解决方法(当然不止这一种)。
虚引用对象可以跟踪对象的回收时间,所以,可以将一些资源释放操作放在虚引用中执行和记录。