java垃圾回收机制

  • 垃圾回收的意义
  • java中的垃圾回收机制
  • 对象在内存中的状态
  • 探秘finalize()方法
  • 强引用、弱引用、软引用、虚引用
垃圾回收的意义

也许是保护环境吧。

人类生活生产,总会产生各种各样的垃圾,这些垃圾都怎么处理?一般无外乎挑挑拣拣,分分类,能回收的当然好,回来洗洗消毒继续用;不能回收的也没办法,埋了烧了?总之是对外界有一些不好的影响,这些不好的影响在程序猿看来,就是系统泄露,更具体一点就是内存泄漏了。

有过C/C++编程经历的童鞋,都有一个很头疼的问题:需要为自己分配的内存负责,从生到死都得由自己来控制。一般说来,显示地垃圾回收是一个比较蛋疼的事情,“我哪知道知道内存啥时候被释放啊”,这估计也是大多数程序猿初识C++时的碎碎念。

不能回收的垃圾破坏了地球的生态系统,不能回收的内存造成计算机运行缓慢,严重一点系统就直接崩溃了。

总的来说,显示回收有三个不太优雅的地方:

  • 回收不及时:对象的释放时间不定,内存碎片的清理时间不定
  • 错误回收:一不小心回收了程序核心类库占用的内存,岂不是天塌地陷?
java中的垃圾回收机制

首先要明白,回收机制回收的是啥?

应该都知道,java中有栈内存和堆内存之别,栈内存主要存放一些变量的引用,而堆内存则是一个运行的数据区,类的实例一般都保存在这里。JVM中提供了一个垃圾回收器——一种动态存储管理技术,来管理这些堆内存,当一个jvm觉得一个对象不再有用或者内存块之间有不少空闲区时,就会执行特定的回收算法来将其除掉。

相对C/C++来说,java的垃圾回收没那么事逼了,它不需要程序猿直接控制——所有的分配和回收都是由jre在后台悄悄帮你办好了,你可以干预,但最终回不回收,何时回收,怎么回收还是由jvm说了算。一般的,jre会在后台提供一个线程来监测和计算那些不再使用的内存,当CPU空闲或者内存不足时,回收机制就开始崭露头角了。

这样,java程序猿就不用再考虑对象的存储问题,编程效率自然上来,程序也更加美观完整了。

当然,金无足赤,这样优雅的实现依然有其弊端。许多事情表面上之所以风轻云淡,那是因为在你看不到的地方,有人默默帮你做了那些并不轻松的事情。jre提供了后台的守护线程,默默地将我们丢下的每一片垃圾捡起、整理、重新分配给后来人。这样的开销势必会影响程序的性能:jvm要区分出来程序中的有用和无用对象,势必就要追踪分析每一个对象的状态;垃圾回收算法的不完善,也不能保证100%回收到所有的废弃内存。

值得提出的是,java并没有明确说明JVM使用哪种算法。有兴趣研究的的参考一下这篇文章:http://blog.csdn.net/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。

先总结这么多,有疑问的欢迎吐槽拍砖。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值