- 垃圾回收的意义
- java中的垃圾回收机制
- 对象在内存中的状态
- 探秘finalize()方法
- 强引用、弱引用、软引用、虚引用
垃圾回收的意义
也许是保护环境吧。
人类生活生产,总会产生各种各样的垃圾,这些垃圾都怎么处理?一般无外乎挑挑拣拣,分分类,能回收的当然好,回来洗洗消毒继续用;不能回收的也没办法,埋了烧了?总之是对外界有一些不好的影响,这些不好的影响在程序猿看来,就是系统泄露,更具体一点就是内存泄漏了。
有过C/C++编程经历的童鞋,都有一个很头疼的问题:需要为自己分配的内存负责,从生到死都得由自己来控制。一般说来,显示地垃圾回收是一个比较蛋疼的事情,“我哪知道知道内存啥时候被释放啊”,这估计也是大多数程序猿初识C++时的碎碎念。
不能回收的垃圾破坏了地球的生态系统,不能回收的内存造成计算机运行缓慢,严重一点系统就直接崩溃了。
总的来说,显示回收有三个不太优雅的地方:
- 回收不及时:对象的释放时间不定,内存碎片的清理时间不定
- 错误回收:一不小心回收了程序核心类库占用的内存,岂不是天塌地陷?
java中的垃圾回收机制
首先要明白,回收机制回收的是啥?
应该都知道,java中有栈内存和堆内存之别,栈内存主要存放一些变量的引用,而堆内存则是一个运行的数据区,类的实例一般都保存在这里。JVM中提供了一个垃圾回收器——一种动态存储管理技术,来管理这些堆内存,当一个jvm觉得一个对象不再有用或者内存块之间有不少空闲区时,就会执行特定的回收算法来将其除掉。
相对C/C++来说,java的垃圾回收没那么事逼了,它不需要程序猿直接控制——所有的分配和回收都是由jre在后台悄悄帮你办好了,你可以干预,但最终回不回收,何时回收,怎么回收还是由jvm说了算。一般的,jre会在后台提供一个线程来监测和计算那些不再使用的内存,当CPU空闲或者内存不足时,回收机制就开始崭露头角了。
这样,java程序猿就不用再考虑对象的存储问题,编程效率自然上来,程序也更加美观完整了。
当然,金无足赤,这样优雅的实现依然有其弊端。许多事情表面上之所以风轻云淡,那是因为在你看不到的地方,有人默默帮你做了那些并不轻松的事情。jre提供了后台的守护线程,默默地将我们丢下的每一片垃圾捡起、整理、重新分配给后来人。这样的开销势必会影响程序的性能:jvm要区分出来程序中的有用和无用对象,势必就要追踪分析每一个对象的状态;垃圾回收算法的不完善,也不能保证100%回收到所有的废弃内存。
值得提出的是,java并没有明确说明JVM使用哪种算法。有兴趣研究的的参考一下这篇文章:http://blog.youkuaiyun.com/zsuguangh/article/details/6429592
但无论使用哪种算法,无外乎都有这些特点:
- 只能回收堆内存中的对象,对任何物理资源(数据库、网络/磁盘IO等)无能为力;
- 精确的回收时间、回收方式,往往是不确定的。它依赖于具体的算法、具体的配置。
- 能精确标记活着的对象和定位对象之间的引用关系,以免伤及无辜。
- 回收任何对象之前,总会先调用它的finalize()方法,至于为什么,下面再讨论。
对象在内存中的状态
根据它被引用的状态,可分为三种:
- 可达:这个没啥说的;
- 可恢复:不再有变量引用。但调用finalize()方法有可能让一个变量重新引用该对象,则这个对象就会再次变为可达状态,否则就会变成不可达状态;
- 不可达:对象与所有引用的关联已被切断,调用finalize()也没让它复活,永久性地失去引用。只有对象处于不可达状态时,系统才会真正回收该对象所占用的资源。
三者的关系图如下:
下面具体看个例子:
public class InitTest {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
createObject();
}
public static void createObject() {
String a = new String("测试");
a = new String("演示");
}
}
当执行String a = new String(“测试”);时,”测试”对象处于可达状态;执行a = new String(“演示”);时,”演示”对象处于可达状态,而”测试”处于可恢复状态,而并不是不可达状态,系统此时也不会对它进行回收。有的童鞋不免要问,如果我此时强制让它回收会怎么样呢?
强制回收
实际上强制回收的说法并不准确。前面已经提过,系统何时回收它所占用的内存,程序根本不知道,程序能控制的只是保证一个对象何时不再被引用,或者通知系统进行垃圾回收,至于垃圾何时被回收,仍然由jvm来决定。
强制回收一般有两种方式:
- 调用System的gc()方法;
- 调用Runtime的gc()方法;Runtime.getRuntime().gc()
探秘finalize()方法
java中垃圾回收绕不开的一个方法。它是一个对象被回收之前,最后一次可达的机会。当finalize()方法执行后,对象会消失,垃圾回收机制开始执行。
有过编程经验的可能都注意到了,finalize()方法定义在Object类中,这就意味着所有的类都可以重写它来实习自己的清理机制。如果一个程序终止之前都没有执行垃圾回收,那么finalize()方法则不会被调用。所以finalize()方法是否被调用也具有不确定性。
所以永远不要在finalize()中清理资源,也永远不要主动调用这个方法。看一下下面的例子:
public class FinalizeTest {
protected static FinalizeTest ft = null;
public void info() {
System.out.println("测试finalize方法");
}
public static void main(String[] args) {
// TODO Auto-generated method stub
new FinalizeTest();
// 通知系统进行资源回收
// System.gc();
System.runFinalization();//强制执行finalize()方法
ft.info();
}
@Override
public void finalize() {
ft = this;
}
}
上面的程序new FinalizeTest();之后,并未有任何引用,新创建的对象会进入可恢复状态。调用System.gc(),并不能保证finalize()马上被执行,如果注释了System.runFinalization(),就会报空指针异常。
强引用、弱引用、软引用、虚引用、引用队列
垃圾回收机制中对对象的引用是一个很重要的判定标准。根据引用的级别(这一点和安卓中的视图销毁差不多),java中又可将引用分为四种:
- 强引用:毋庸赘述,最常见的引用方式,创建一个对象,把对象赋给一个引用变量,这样的引用就是强引用。
- 软引用:SoftReference类实现,当一个对象只有软引用时,它有可能被回收,如果系统内存不足的话。
- 弱引用:WeakRefernce类实现,与上面的软引用类似,只是引用级别更低。如果一个对象处于弱引用状态,当垃圾回收机制运行,总是会回收该对象所占用的内存,而不管内存是否充足。
- 虚引用:PhantomReference类实现,级别最弱,类似于没有引用。它的作用主要用来跟踪对象被垃圾回收的状态,无法单独使用,必须陪护引用队列。
- 引用队列:用来保存被回收后对象的引用。程序可检查与虚引用关联的引用队列中是否包含了改虚引用,从而知道虚引用所指对象是否即将被回收。
瞅一眼下面的例子:
public class ReferenceTest {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
String str = new String("引用测试");
// 创建一个弱引用,引用到"引用测试"
WeakReference weakReference = new WeakReference(str);
// 断开str和"引用测试"之间的引用
str = null;
// 打印弱引用对象
System.out.println(weakReference.get());
// 强制通知回收
System.gc();
System.runFinalization();
// 再次打印弱引用对象
System.out.println(weakReference.get());
}
}
输入:
引用测试
null
再看一个虚引用的例子,两者比较一下:
public class PhantomReferenceTest {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
String str = new String("虚引用测试");
// 创建一个引用队列
ReferenceQueue referenceQueue = new ReferenceQueue();
// 创建一个指向"虚引用测试"的虚引用
PhantomReference phantomReference = new PhantomReference(str,
referenceQueue);
// 断开str和"虚引用测试"之间的引用
str = null;
System.out.println(phantomReference.get());
// 强制回收
System.gc();
System.runFinalization();
// 取出最先进入队列的引用和phantomReference比较
System.out.println(referenceQueue.poll() == phantomReference);
}
}
输出:
null
true
再次强调几点:
- 强引用与其余的中引用不能共存,如上例的“str=null”;
- 虚引用不能单独使用,所以PhantomReferenceTest 中会输出null;
- 强制回收后,虚引用的字符串被回收,对应的虚引用添加到关联的引用队列中,故最后输出为true。
先总结这么多,有疑问的欢迎吐槽拍砖。