`

Java析构函数

阅读更多
copy:http://blog.csai.cn/user1/14699/archives/2007/14755.html


许多方面,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");
    }
}


分享到:
评论

相关推荐

    java kok 构造与析构函数

    在Java编程语言中,"构造函数"和"析构函数"是两个关键概念,它们在对象的生命周期管理中起着至关重要的作用。首先,我们来深入理解这两个概念。 构造函数,顾名思义,是在创建类的对象时被调用的特殊方法。它的主要...

    C#编程艺术:构造函数与析构函数的奥秘

    ### C#编程艺术:构造函数与析构函数的奥秘 C#作为一种强大的面向对象的编程语言,在软件开发领域占据着举足轻重的地位。它不仅受到C++和Java的影响,还结合了现代编程语言的优点,如垃圾回收、类型安全、泛型支持...

    dotnet C# 如果在构造函数抛出异常 析构函数是否会执行.rar

    本话题聚焦于一个特定的编程概念:当在构造函数中抛出异常时,析构函数是否会执行。这个问题涉及到类对象的生命周期、异常处理以及垃圾回收机制。 首先,我们需要了解构造函数和析构函数的基本概念。构造函数是类的...

    当析构函数遇到多线程── C++ 中线程安全的对象回调 PDF

    ### 当析构函数遇到多线程——C++中线程安全的对象回调 #### 1. 多线程下的对象生命期管理 C++作为一种需要程序员手动管理对象生命周期的语言,在多线程环境中尤其需要谨慎处理对象的创建和销毁过程。由于多线程...

    C++箴言:避免析构函数调用虚函数[归类].pdf

    在C++编程中,有一条重要的原则是避免在构造函数或析构函数中调用虚函数。这条箴言尤其对那些从C#或Java转向C++的开发者来说可能显得有些反直觉,因为在这些语言中,这样的调用通常是允许的。然而,在C++中,这样做...

    Java与C++在虚函数,纯虚函数上的区别

    在Java中,没有显式的析构函数概念,资源管理通常通过垃圾回收机制自动完成。因此,不需要考虑析构函数的虚实性问题。 总结来说,Java与C++在处理虚函数、纯虚函数和抽象类方面有着根本的不同。Java通过抽象方法和...

    10 个 Java 编码中微妙的最佳实践

    1.牢记C++的析构函数  还记得C++中的析构函数吗?不记得了?或许你真的很幸运,因为你再也不必为删除...同样,在JAVA代码中,当你处理如下类析构函数语法的时候,也要把这个特性牢记在心: 解压密码 www.jiangyea.com

    php构造函数与析构函数

    构造函数主要用于在对象创建后立即进行成员变量的初始化,而析构函数则在对象被销毁之前进行必要的清理工作。 构造函数在PHP5中被重新定义,使用了魔术方法__construct(),这是为了与类名解耦,并且在类名改变时...

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

    在Java编程语言中,没有直接定义为“文本析构函数”的特定内置函数,但我们可以利用一系列方法和类来实现类似的功能。这个过程通常涉及到字符串的处理、正则表达式、字符流或缓冲区,以及集合类的使用。 1. **字符...

    最新c++笔试面试宝典

    6. **析构函数**:析构函数在对象生命周期结束时自动调用,子类析构函数会先于父类析构函数调用。基类的析构函数通常是虚函数,以确保正确地销毁多态对象。 7. **多态、虚函数和纯虚函数**:虚函数允许子类覆盖父类...

    经典C++ 觉得可以的

    这是因为析构函数负责清理对象的状态,基类的析构函数在派生类的析构函数之后调用,确保在基类析构前派生类的所有成员已经被正确清理。 总之,理解并掌握C++中的动态内存管理、继承机制以及面向对象特性对于编程...

    C++试题宝典 C++试题宝典

    默认情况下,C++编译器会自动插入对基类析构函数的调用,但如果子类析构函数是虚函数,这一调用必须显式进行。 总结,理解和掌握这些C++的核心知识点对于准备C++面试或提升编程技能至关重要。通过不断练习和实践,...

    分数类中的操作符重载

    这可以通过在分数类中添加一个构造函数,接受一个整数并将其作为分子,分母设为1,或者通过重载操作符使整数可以隐式转换为分数对象来实现。 在`fraction`文件中,可能包含了这个分数类的实现细节,包括类定义、...

    C_c++语言面试宝典(保证你通过面试).pdf

    - `delete` 会调用非数组对象的析构函数一次,而 `delete[]` 会为数组中的每个元素调用析构函数,然后释放整个内存块。不正确地匹配可能导致只调用一次析构函数,导致数组中其他对象的资源未被正确释放。 3. **C++...

    c++笔试面试宝典2010版

    6. **析构函数的调用顺序**:析构函数的调用遵循反向构造原则,即先调用派生类的析构函数,再调用基类的析构函数,确保基类析构时派生类的对象已经完全清理。 7. **深拷贝和浅拷贝**:深拷贝会复制对象的所有内容,...

    C++\C++面试宝典(1).

    - 析构函数的调用遵循反向构造顺序,即先调用派生类的析构函数,再调用基类的析构函数。这确保在基类析构时,派生类的数据已经正确清理。 7. 构造和析构的配合: - 当创建对象时,首先调用基类的构造函数,然后是...

    最新c++面试127题,看你会多少1.pdf

    - `new[]` 和 `delete[]` 用于动态分配和释放数组,`delete[]` 会调用数组中每个元素的析构函数,而`delete` 只会调用单个对象的析构函数。 2. **`delete` 与 `delete[]` 的区别**: - `delete` 仅适用于单个对象...

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

    在Java中,没有直接的析构函数概念,而是依赖垃圾回收器(Garbage Collector)来回收不再使用的对象。`finalize()`方法虽然与析构函数类似,但其执行时间不确定,不保证在每次对象销毁时都会调用。同样,Java没有虚...

Global site tag (gtag.js) - Google Analytics