【问题标题】:Java and C++ on Stack Unwinding issue堆栈展开问题上的 Java 和 C++
【发布时间】:2010-03-31 08:35:05
【问题描述】:

据我所知,如果发生未捕获的异常,C++ 会立即销毁局部变量,Java 释放引用并将其余部分留给垃圾收集器。

这是对的吗?在这个问题上,Java 和 C++ 到底有什么区别?换句话说,就堆栈展开问题而言,这两种语言中的哪一种被认为更好? :)

【问题讨论】:

  • 主观 - 定义“更好”。
  • Java 实现了一个适当的垃圾收集器,因此理论上,您永远不必担心对象不会破坏它在堆栈上创建的所有内容。 C++ 假设你很小心。
  • Java 实现了一个 memory 垃圾收集器。临时文件不会被垃圾回收,因此可能会因堆栈展开而泄漏。另一方面,在 C++ 堆栈展开调用析构函数,除了内存清理,还可以清理临时文件和其他资源。所以 Java 更简单,C++ 更灵活,这是一个标准的工程权衡。
  • @MSalters:+1 因为它阐明了 C++ 和 Java 在资源处理方面的根本区别。

标签: java c++ stack-unwinding


【解决方案1】:

我会因此而发火,但是......

在堆栈展开方面,C++ 比 Java 更胜一筹——根本没有竞争对手。 C++ 对象析构函数一直沿堆栈返回,直到到达捕获点——沿途优雅地释放所有托管资源。

正如您所说,Java 将所有这些都交给了非确定性垃圾收集器(最坏的情况),或者由您在代码中乱扔的任何明确制作的 finally 块(因为 Java 不支持真正的 RAII)。也就是说,所有的资源管理代码都在每个类的客户手中,而不是在应该在的类设计者手中。

也就是说,在 C++ 中,堆栈展开机制只有在您小心确保析构函数本身不会发出异常时才能正常工作。一旦您有两个活动异常,您的程序 abort() 就不会通过 go(当然也不会触发任何剩余的析构函数)。

【讨论】:

  • @Mnementh:堆对象除外,这些对象由具有析构函数的对象正确拥有。
  • @Mnementh:在 C++ 中,你只需要遵守纪律:堆分配由智能指针持有,句号。结束内存管理问题。抱歉,我并不是要对 Java 大喊大叫,但有时我觉得他们通过删除 C++ 的所有优秀部分创造了“更好的 C++”。
  • @WizardOfOdds:您的用户关心释放对象吗?废话。他们关心应用程序不崩溃,这在 Java 中显然更容易做到,关心性能,这可能有点困难,但肯定不是不可能的。 GC 暂停问题通常可以通过调整 JVM 的 GC 选项来解决。
  • @Drew Hall:智能指针的自我限制?为什么要删掉 C++ 中好的部分? ;-)
  • 关于这个 RAII 的事情,如果我们在 Java 中遇到资源问题(例如,数据库连接),为什么它没有在垃圾收集器收集对象时调用的类似析构函数的机制.这样,无论何时,我们都会保证资源将被关闭。对吗?
【解决方案2】:

堆栈展开是专门调用调用链中所有完全构造的对象的析构函数,直到捕获到异常为止。

Java 根本没有堆栈展开——如果抛出异常,它不会对对象做任何事情。您必须自己处理 catchfinally 块中的对象。这就是 C# 引入 using statements 的主要原因 - 它们简化了 IDisposable.Dispose() 的调用,但同样,这并不是 C++ 堆栈展开的完全替代。

【讨论】:

  • 在这里问这个问题可能会更好:为什么Java没有类似析构函数的机制,当垃圾收集器收集对象时会调用该机制?这样,我们就可以保证我们的资源是封闭的;无论何时,因为我们对处理内存泄漏更感兴趣。
【解决方案3】:

你说的很对,C++ 以相反的顺序销毁所有局部变量,因为它退出堆栈上的每个函数 - 就像你以编程方式执行 return - 并退出 main()

【讨论】:

    【解决方案4】:

    对于堆栈,两者都做同样的事情:它们为您留下的异常块释放堆栈。在 Java 中,所有原始类型(int、double 等)都直接保存,这种类型的局部变量在这一刻被释放。所有对象都通过局部变量中的引用保存,因此引用被删除,但对象本身仍保留在堆上。如果这是对该对象的最后一次引用,它们将在下一次垃圾回收时释放。如果在 C++ 中是在堆上创建的对象并且局部变量保留一个指针,那么堆上的对象不会自动释放,它们会永远留在堆上(是的,你会得到一个内存泄漏)。如果您已将对象保存在堆栈上,则调用析构函数(并可能释放堆上的其他引用对象)。

    【讨论】:

    • 没错,但是 C++ 中的智能指针也可以让您自动销毁堆分配的对象。
    • 事实上它们的工作方式完全相同。堆栈展开调用它们的析构函数,它们的析构函数依次销毁相应的堆分配对象。
    • 是的,这就是它们的实现方式。我的意思是它们的工作方式不同,因为您不需要非常小心地使用智能指针来避免内存泄漏。
    • 我猜,sharptooth 想说的是,您也应该在讨论中提到智能指针。类似于:对象仍在堆上,但基于堆栈的智能指针会尽快释放它
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-17
    • 2015-12-18
    • 2014-08-06
    • 1970-01-01
    • 2011-07-10
    相关资源
    最近更新 更多