`
pterodactyl
  • 浏览: 766613 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

java析构函数替代者finalize()解说

    博客分类:
  • java
阅读更多

java析构函数替代者finalize()解说

许多方面,Java 类似于 C++。Java 的语法非常类似于 C++,Java 有类、方法和数据成员;Java 的类有构造函数; Java 有异常处理。

 但是,如果你使用过 C++ 会发现 Java 也丢掉一些可能是你熟悉的特性。这些特性之一就是析构函数。取代使用析构函数,Java 支持finalize() 方法。

 在本文中,我们将描述 finalize() 与 C++ 析构函数的区别。另外,我们将创建一个简单的 Applet 来演示 finalize() 是如何工作的。

最终的界限

    与 Java 不同,C++ 支持局部对象(基于栈)和全局对象(基于堆)。因为这一双重支持,C++ 也提供了自动构造和析构,这导致了对构造函数和析构函数的调用,(对于堆对象)就是内存的分配和释放。

    在 Java 中,所有对象都驻留在堆内存,因此局部对象就不存在。结果,Java 的设计者觉得不需要析构函数(象 C++ 中所实现的)。

    取而代之,Java 定义了一个特殊的方法叫做finalize() ,它提供了 C++ 析构函数的一些功能。但是,finalize() 并不完全与 C++ 的析构函数一样,并可以假设它会导致一系列的问题。finalize() 方法作用的一个关键元素是 Java 的垃圾回收器。

垃圾回收器

    在 C/C++、Pascal和其他几种多种用途的编程语言中,开发者有责任在内存管理上发挥积极的作用。例如,如果你为一个对象或数据结构分配了内存,那么当你不再使用它时必须释放掉该内存。

    在 Java 中,当你创建一个对象时,Java 虚拟机(JVM)为该对象分配内存、调用构造函数并开始跟踪你使用的对象。当你停止使用一个对象(就是说,当没有对该对象有效的引用时),JVM 通过垃圾回收器将该对象标记为释放状态。

    当垃圾回收器将要释放一个对象的内存时,它调用该对象的finalize() 方法(如果该对象定义了此方法)。垃圾回收器以独立的低优先级的方式运行,只有当其他线程挂起等待该内存释放的情况出现时,它才开始运行释放对象的内存。(事实上,你可以调用System.gc() 方法强制垃圾回收器来释放这些对象的内存。)

   在以上的描述中,有一些重要的事情需要注意。首先,只有当垃圾回收器释放该对象的内存时,才会执行finalize()。如果在 Applet 或应用程序退出之前垃圾回收器没有释放内存,垃圾回收器将不会调用finalize()。

    其次,除非垃圾回收器认为你的 Applet 或应用程序需要额外的内存,否则它不会试图释放不再使用的对象的内存。换句话说,这是完全可能的:一个 Applet 给少量的对象分配内存,没有造成严重的内存需求,于是垃圾回收器没有释放这些对象的内存就退出了。

    显然,如果你为某个对象定义了finalize() 方法,JVM 可能不会调用它,因为垃圾回收器不曾释放过那些对象的内存。调用System.gc() 也不会起作用,因为它仅仅是给 JVM 一个建议而不是命令。

finalize() 有什么优点呢?

    如果finalize() 不是析构函数,JVM 不一定会调用它,你可能会疑惑它是否在任何情况下都有好处。事实上,在 Java 1.0 中它并没有太多的优点。

    根据 Java 文档,finalize() 是一个用于释放非 Java 资源的方法。但是,JVM 有很大的可能不调用对象的finalize() 方法,因此很难证明使用该方法释放资源是有效的。

      Java 1.1 通过提供一个System.runFinalizersOnExit() 方法部分地解决了这个问题。(不要将这个方法与 Java 1.0 中的System.runFinalizations() 方法相混淆。)不象System.gc() 方法那样,System.runFinalizersOnExit() 方法并不立即试图启动垃圾回收器。而是当应用程序或 Applet 退出时,它调用每个对象的finalize() 方法。

    正如你可能猜测的那样,通过调用System.runFinalizersOnExit() 方法强制垃圾回收器清除所有独立对象的内存,当清除代码执行时可能会引起明显的延迟。现在建立一个示例 Applet 来演示 Java 垃圾回收器和finalize() 方法是如何相互作用的。

回收垃圾

    通过使用Java Applet Wizard 创建一个新的 Applet 开始。当提示这样做时,输入 final_things 作为 Applet 名,并选择不要生成源文件注释。

    接下来,在Java Applet Wizard 进行第三步,不要选择多线程选项。在第五步之前,根据需要修改 Applet 的描述。

    当你单击Finish 后,Applet Wizard 将生成一个新的工作空间,并为该项目创建缺省的 Java 文件。从列表 A 中选择适当的代码输入(我们已经突出显示了你需要输入的代码)。

    当你完成代码的输入后,配置Internet 浏览器将System.out 的输出信息写到Javalog.txt 文件中。(在IE 选项对话框的高级页面中选择起用 Java Logging。)

编译并运行该 Applet。然后,等待 Applet 运行(你将在状态栏中看到 Applet 已启动的信息),退出浏览器,并打开Javalog.txt 文件。你将会发现类似于下列行的信息:

        1000 things constructed

        0 things finalized

    正如你能够看到的那样,建立了1,000个对象仍然没有迫使垃圾回收器开始回收空间,即使在 Applet 退出时也没有对象被使用。

    现在,删除在stop() 方法第一行中的注释符以起用System.gc() 方法。再次编译并运行该 Applet ,等待 Applet 完成运行,并退出浏览器。当你再次打开Javalog.txt 文件,你将看到下列行:

        1000 things constructed

        963 things finalized

    这次,垃圾回收器认为大多数对象未被使用,并将它们回收。按顺序,当垃圾回收器开始释放这些对象的内存时,JVM 调用它们的finalize() 方法。

继承finalize()?

    顺便,如果你在类中定义了finalize() ,它将不会自动调用基类中的方法。在我们讨论了finalize() 与 C++ 的析构函数的不同点后,对这个结论不会惊讶,因为为某个类定制的清除代码另一个类不一定会需要。

    如果你决定要通过派生一个类的finalize() 方法来调用基类中的finalize() 方法,你可以象其他继承方法一样处理。

        protected void finalize()

        {

          super.finalize();

          // other finalization code...

        }

    除了允许你控制是否执行清除操作外,这个技术还使你可以控制当前类的finalize() 方法何时执行。

结论

    然而有益的是,Java 的自动垃圾回收器不会失去平衡。作为便利的代价,你不得不放弃对系统资源释放的控制。不象 C++ 中的析构函数,Java Applet 不会自动执行你的类中的finalize() 方法。事实上,如果你正在使用 Java 1.0,即使你试图强制它调用finalize() 方法,也不能确保将调用它。

    因此,你不应当依靠finalize() 来执行你的 Applet 和应用程序的资源清除工作。取而代之,你应当明确的清除那些资源或创建一个try...finally 块(或类似的机制)来实现。

列表 A: final_things.java

import java.applet.*;

import java.awt.*;

class thing

{
  public static int thingcount = 0;

  public static int thingfinal = 0;

  public thing()

  {

    ++thingcount;

  }

  protected void finalize()

  {

    ++thingfinal;

  }

}

public class final_things extends Applet

{

  public final_things()

  {

  }

  public String getAppletInfo()

  {

    return "Name: final_thing\r\n" +

           "Author: Tim Gooch\r\n" +

           "Created with Microsoft " +

           "Visual J++ Version 1.1";

  }

  public void init()

  {

      resize(320, 240);

  }

  public void destroy()

  {

  }

  public void paint(Graphics g)

  {

    g.drawString("Created with Microsoft" +

      "Visual J++ Version 1.1", 10, 20);

  }

  public void start()

  {

    while(thing.thingfinal < 1)

    {

      new thing();

    }

  }

  public void stop()

  {

    // System.gc();

    System.out.println(thing.thingcount +

      " things constructed");

    System.out.println(thing.thingfinal +

      " things finalized");

  }

}

 

==============================

此时,大家可能已相信了自己应该将finalize()作为一种常规用途的清除方法使用。它有什么好处呢?
  要记住的第三个重点是:
  垃圾收集只跟内存有关!
  也就是说,垃圾收集器存在的唯一原因是为了回收程序不再使用的内存。所以对于与垃圾收集有关的任何活动来说,其中最值得注意的是finalize()方法,它们也必须同内存以及它的回收有关。
  但这是否意味着假如对象包含了其他对象,finalize()就应该明确释放那些对象呢?答案是否定的――垃圾收集器会负责释放所有对象占据的内存,无论这些对象是如何创建的。它将对finalize()的需求限制到特殊的情况。在这种情况下,我们的对象可采用与创建对象时不同的方法分配一些存储空间。但大家或许会注意到,Java中的所有东西都是对象,所以这到底是怎么一回事呢?
  之所以要使用finalize(),看起来似乎是由于有时需要采取与Java的普通方法不同的一种方法,通过分配内存来做一些具有C风格的事情。这主要可以通过“固有方法”来进行,它是从Java里调用非Java方法的一种方式(固有方法的问题在附录A讨论)。C和C++是目前唯一获得固有方法支持的语言。但由于它们能调用通过其他语言编写的子程序,所以能够有效地调用任何东西。在非Java代码内部,也许能调用C的malloc()系列函数,用它分配存储空间。而且除非调用了free(),否则存储空间不会得到释放,从而造成内存“漏洞”的出现。当然,free()是一个C和C++函数,所以我们需要在finalize()内部的一个固有方法中调用它。
  读完上述文字后,大家或许已弄清楚了自己不必过多地使用finalize()。这个思想是正确的;它并不是进行普通清除工作的理想场所。

分享到:
评论

相关推荐

    C#析构函数

    4. 析构函数与Finalize方法的关系:析构函数在内部被.NET运行时转换为受保护的override方法Finalize。析构函数的执行会隐式地调用基类的Finalize方法,从而确保在对象生命周期结束时,所有基类的析构逻辑也能被执行...

    java kok 构造与析构函数

    总的来说,理解和掌握构造函数、析构函数(以及在Java中的`finalize()`方法)、函数的重载、类的继承以及函数覆盖,对于提升Java编程能力至关重要。同时,设计模式的运用可以提高代码的可维护性和可扩展性,使得大型...

    全面解读C#编程中的析构函数用法

    析构函数用于析构类的实例。 备注 不能在结构中定义析构函数。...该析构函数隐式地对对象的基类调用 Finalize。这样,前面的析构函数代码被隐式地转换为以下代码: protected override void Finalize() { try {

    c#析构构造函数c#析构构造函数c#析构构造函数

    在某些情况下,如果你确实需要使用析构函数,记得在类声明前添加`[SuppressFinalize]`属性,这样可以通知垃圾回收器无需调用`Finalize()`方法(这是析构函数在.NET中的另一个名字),因为资源的清理工作已经在析构...

    Java中finalize()的用法

    finalize() 方法相当于析构函数,它是垃圾回收器回收一个对象时第一个要调用的方法。finalize() 方法的主要作用是释放对象占用的系统资源,例如文件句柄、window 字符字体等非 Java 资源。 在 Java 中,finalize() ...

    ruby-destructor:Ruby 的析构函数

    析构函数 这将新的回调添加到实例对象,例如 :initialize,但会产生相反的效果:finalize 安装 将此行添加到应用程序的 Gemfile 中: gem 'destructor' 然后执行: $ bundle 或者自己安装: $ gem install ...

    C#学习笔记整理_深入剖析构造函数、析构函数

    构造函数和析构函数在C#中起着关键作用,构造函数用于初始化对象状态,而析构函数则负责清理工作。了解它们的工作原理和最佳实践,有助于编写更健壮、高效且易于维护的代码。同时,对于非托管资源的管理,理解`...

    TD:文本析构函数是用于拆分文本并显示其单词的程序

    但在Java中,没有真正的析构函数,我们使用`finalize()`方法作为替代,但这不适用于这里的情况。若要创建一个“文本析构”功能,可以定义一个方法,比如`deconstructText()`,专门用于拆分和显示文本中的单词。 6. ...

    浅析JAVA与C++的区别.pdf

    在Java中,没有析构函数,但有finalize方法,它可以在对象被垃圾回收之前被调用,以释放资源。 三、数据类型 C++和Java中的数据类型不同。C++有多种基本类型,如整数、浮点数、字符等,另外还有结构体、联合体、...

    Java中final,finally,finalize三个关键字的区别_动力节点Java学院整理

    Java 中没有析构函数,而 C++ 中的析构函数是在对象消亡时运行的。finalize() 方法主要用途是回收特殊渠道申请的内存,例如 JNI(Java Native Interface)调用 non-Java 程序(C 或 C++)。 final 关键字用于限制...

    java中 final, finally, finalize 的区别

    java中final, finally, finalize 的区别

    著名企业c+c++数据结构算法面试笔试题

    `finalize()`方法虽然与析构函数类似,但其执行时间不确定,不保证在每次对象销毁时都会调用。同样,Java没有虚函数的概念,但有接口(interface)和抽象方法(abstract method)来实现类似的功能。 【全局变量和...

    Java中finalize方法.pdf

    在垃圾回收器准备回收一个不再被引用的对象时,会尝试调用该对象的`finalize()`方法,这个过程类似于C++中的析构函数,但并非完全等价。 垃圾回收是Java虚拟机(JVM)的一项核心功能,它的主要任务是回收那些不再...

    NETFramework垃圾回收3.5Version分享.pdf

    9. **C#析构函数**:C#中,析构函数用于实现`Finalize`方法,但不直接等同于C++的析构函数。在.NET 2.0以后,Visual C++提供了不同的语法来实现`Finalize`。 10. **WeakReference类**:`System.WeakReference`类...

    .NETFramework垃圾回收3.5Version.pdf

    - 析构函数并非直接对应C++的析构函数,而是.NET的`Finalize`方法的语法糖,用于清理非托管资源。 2. **弱引用(WeakReference)**: - 弱引用允许程序保持对对象的引用,但不会阻止垃圾回收器回收该对象。这意味着...

    关于finalize机制和引用、引用队列的用法详解

    C++有析构函数这个东西,能够很好地在对象销毁前做一些释放外部资源的工作,但是java没有。Object.finalize()提供了与析构函数类似的机制,但是它不安全、会导致严重的内存消耗和性能降低,应该避免使用。best ...

    java 基础和j2ee面试题集

    5. 析构函数和虚函数:Java没有析构函数,但有finalize()方法,用于在对象被垃圾回收前执行清理工作。与C++的析构函数不同,Java的finalize()并非一定会被调用。虚函数在Java中体现为接口和抽象方法,通过多态实现。...

    java 面试题目之一

    Java没有析构函数,但有finalize()方法,尽管不推荐用于资源清理。Java中的接口和抽象类实现类似C++虚函数的功能。 6. Error与Exception:Error是程序无法处理的严重问题,如系统崩溃、硬件故障。Exception是程序...

    C_C#_VB_Java语法比较总结

    在进行C、C#、VB和Java这四种常见编程语言的语法比较时,我们通常关注的是它们在基本数据类型、变量声明、常量声明、数组声明、函数声明、名域(或包)、析构函数、运算符重载以及面向对象编程方面的差异。...

Global site tag (gtag.js) - Google Analytics