【Java学习笔记(五-1)】对象与垃圾回收

Java垃圾回收机制的特征

  • 垃圾回收机制只负责回收堆内存中的对象,不会回收任何物理资源(例如数据库连接、网络IO等资源)
  • 程序无法精确控制垃圾回收的运行,垃圾回收会在何时的时候进行。当对象永久性的失去引用后,系统就会在合适的时候回收它所占用的内存
  • 在垃圾回收机制回收任何对象之前,总会调用它的finalize()方法,该方法可能使该对象重新复活(让一个引用变量重新引用该对象),从而导致垃圾回收机制取消

对象在内存中的状态

根据它被引用变量所引用的状态,可以把它所处的状态分成如下三种

  • 可达状态:有一个以上的引用变量引用它
  • 可恢复状态:程序中不再有任何引用变量引用它。此时系统的垃圾回收机制准备回收该对象所占用的内存,在此之前,系统会调用所有此状态下的对象的finalize()方法进行资源清理。如果系统调用finalize()方法时重新让一个引用变量引用该对象,则这个对象再次变为可达状态;否则变为不可达状态
  • 不可达状态:当对象与所有引用变量的关联被切断,且没有重新变成可达状态时,此对象永久性失去引用,此时系统会真正回收该对象所占有的资源

强制垃圾回收

程序无法精确控制Java垃圾回收的时机,但依然可以强制系统进行垃圾回收——这种强制只是通知系统进行垃圾回收,但系统是否进行垃圾回收依然不确定

强制系统垃圾回收的方式

  • 调用System类的gc()静态方法:System.gc()
  • 调用Runtime对象的gc()实例方法:Runtime.getRuntime().gc();

finalize方法

在没有明确指定清理资源的情况下,Java提供了默认机制来清理该对象的资源,这个机制就是finalize()方法,其原型为

  • protected void finalize() throws Throwable
  • 当此方法返回后,对象消失,垃圾回收机制开始执行。方法原型中的throws Throwable表示它可以抛出任何类型的异常

垃圾回收机制何时调用对象的finalize()方法是完全透明的,只有当程序认为需要更多的额外内存时,垃圾回收机制才会进行垃圾回收

finalize方法的四个特点

  • 永远不要主动调用某个对象的finalize方法,该方法交给垃圾回收机制调用
  • finalize方法何时被调用,是否被调用具有不确定性
  • 当JVM执行可恢复对象的finalize方法时,可能使该对象或系统中其他对象重新变成可达状态
  • 当JVM执行finalize方法时出现异常时,垃圾回收机制不会报告异常,程序继续执行

对象的软、弱和虚引用

Java中对象的四种引用方式

  • 强引用(StrongReference)

    • 这是Java程序中最常见的引用方式。程序创建一个对象,并把这个对象赋给一个引用变量,程序通过该引用变量操作实际对象
  • 软引用(SoftReference)

    • 软引用需要通过SoftReference类来实现,当一个对象只有软引用时,它有可能被垃圾回收机制回收——当系统内存空间足够时,他不会被系统回收,否则系统可能会回收它。软引用通常用于对内存敏感的程序中
  • 弱引用(WeakReference)

    • 弱引用通过WeakReference类来实现,弱引用和软引用很像,但弱引用的引用级别更低。对于只有弱引用的对象而言,当系统垃圾回收机制运行时,不管系统内存是否足够,总会回收该对象所占用的内存。当然并不是说当一个对象只有弱引用时,它就会被立即回收——正如那些时区引用的对象一样,必须等到系统垃圾回收机制运行时才会被回收
  • 虚引用(PhantomReference)

    • 虚引用通过PhantomReference类实现,虚引用类似于完全没有引用。虚引用对对象本身没有太大影响,对象甚至感觉不到虚引用的存在。如果一个对象只有一个虚引用时吗,那么它和没有引用的效果大致相同。虚引用主要是用于跟踪对象被垃圾回收的状态,虚引用不能单独使用,虚引用必须和引用队列(ReferenceQueue)联合使用

以下范例示范了弱引用所引用的对象被系统垃圾回收过程

  public class ReferenceTest{
   public static void main(String[] args) 
       throws Exception{
       String str=new String("弱引用演示");
       WeakReference wr=new WeakReference(str);
       str=null;
       System.out.println(wr.get());
       System.gc();
       System.runFinalization();
       System.out.println(wr.get());
   }
}
  • 上面的程序首先创建了一个字符串对象”弱引用测试”,并让str引用变量引用它,然后创建了一个弱引用对象,并让该对象和str引用同一个对象。接着切断str和”弱引用测试”之间的联系,但是此程序并不会导致内存紧张,因此JVM不会回收wr所引用的对象,于是wr.get()可以返回”弱引用测试”。接下来调用gc()和runFinalization()方法通知系统回收,此时弱引用wr所引用的对象被回收,wr.get()返回的是null

以下范例使用了虚引用来引用字符串

  public class PhantomReferenceTest{
   public static void main(String[] args) 
       throws Exception{
       String str=new String("虚引用演示");
       ReferenceQueue rq=new ReferenceQueue();
       PhantomReference pr=new PhantomReference(str,rq);
       str=null;
       System.out.println(pr.get());
       System.gc();
       System.runFinalization();
       System.out.println(rq.poll()==pr);
   }
}
  • 因为系统无法通过虚引用来获得被引用的对象,所以pr.get()将返回null。当程序被强制垃圾回收后,只有虚引用的字符串对象将会被垃圾回收,当被引用的对象被回收后,对应的虚引用将被添加到关联的引用队列中,因此rq.poll()==pr的计算结果为true

使用这些引用类可以避免在程序执行期间将对象留在内存中。如果以软引用、弱引用或虚引用的方式引用对象,垃圾回收器就能够随意地释放对象。如果希望尽可能减小程序在其生命周期中所占用的内存大小时,这些引用类就很有用处

必须指出:要是用这些特殊的引用类,就不能保存对对象的强引用;如果保留了对对象的强引用,就会浪费这些引用类所提供的任何好处

由于垃圾回收的不确定性,当程序希望从软、弱引用中取出被引用对象时,可能这个被引用对象已经被释放了。如果程序需要使用那个被引用的对象,就必须重新创建该对象。这个过程可以采用两种方式完成

  • 两种方式的范例

    • obj=wr.get();
      if(obj==null){
         //重新创建一个新的对象,再次让弱引用去引用该对象
         wr=new WeakReference(recreateIt());
         //取出弱引用所引用的对象,将其赋给obj变量
         obj=wr.get();
      }
      ....//操作obj对象
      //再次切断和obj和对象之间的关联
      obj=null;
    • obj=wr.get();
      if(obj==null){
          //重新创建一个新的对象,并使用强引用来引用它
         obj=recreateIt();
         //取出弱引用所引用的对象,将其赋给obj变量
         wr=new WeakReference(obj);
      }
      ....//操作obj对象
      //再次切断和obj和对象之间的关联
      obj=null;
  • 第一种方法存在一定的问题,当if块执行完成后,obj还是有可能为null。因为垃圾回收的不确定性,假设系统在if代码块中间进行垃圾回收,则系统会再次将wr所引用的对象回收,从而导致obj依然为null。第二种方法则不会存在这个问题,if语句执行完之后,obj一定不为null

人已赞赏
Java编程语言

【Java学习笔记(四-1)】关于类、对象、抽象类和接口之间关系的梳理

2020-4-15 21:59:10

DemoJava编程语言

【JavaDemo】③.控制台跑得快

2020-4-22 15:01:12

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
今日签到
有新私信 私信列表
搜索