非内存资源是什么?

18

我正在阅读《Effective Java》。

在讨论finalize时,他说:

C++析构函数也用于回收其他非内存资源。在Java中,try-finally块通常用于此目的。

什么是非内存资源?

数据库连接是一种非内存资源吗?持有数据库连接的对象不也占用一些内存吗?


4
阅读《Effective Java》值得推荐,建议加1。 - oliholz
4个回答

23

数据库连接、网络连接、文件句柄、互斥锁等需要在使用完毕后主动释放(不仅仅是进行垃圾回收)的资源。

是的,这些对象通常会占用一些内存,但关键点是它们还可以(可能是独占的)访问除内存之外的某些资源。


2
是的,最重要的是这些资源与内存一样重要,甚至更重要。 - Thanh DK
我不会说它们更重要。我会认为内存是最难管理的,也最有可能对系统的其余部分产生巨大的负面影响,因此是开始使用受控资源的最佳位置。 - nicodemus13
只是为了明确 - 许多这些东西确实占用了它们自己进程中的内存,而不仅仅是您的代码正在运行的JVM,在这种情况下,从JVM内部“释放”它们也将允许释放非JVM内存(以及其他东西)。 - GreenieMeanie

5
一个数据库连接是一种非内存资源吗?
是的,这是最常见的例子之一。其他示例包括文件句柄、本地 GUI 对象(如 Swing 或 AWT 窗口)和套接字。
持有数据库连接的对象难道不会占用一些内存吗?
是的,但关键是还需要释放非内存部分的资源,并且通常比对象使用的相对较小的内存数量更为稀缺。通常,这些对象具有一个 finalize() 方法来释放非内存资源,但问题在于这些终结器仅在对象被垃圾收集时运行。
由于这些对象很小,因此可能有大量可用的堆内存,以便垃圾收集器很少运行。在垃圾收集器运行期间之间,非内存资源不会被释放,导致可能会耗尽它们。
这甚至可能会导致单个对象出现问题:例如,如果您想通过打开文件、打开目标文件、复制数据然后删除原始文件来在文件系统之间移动文件,则如果该文件仍处于打开状态(而且如果您只将输入流的引用设置为空并且不显式调用 close(),则几乎肯定会),则删除将失败,因为垃圾收集器极不可能在对象变得有资格进行垃圾收集并调用 delete() 之间的正好正确的点运行。

0

1
重要的是要理解这个概念,我认为一份有用的21页文件,涉及内存管理的重要性,不会因为太大而让我失分。我们在这个论坛中清楚地看到了像你这样声誉很高的人喜欢打压别人的情况。 - Bitmap
因为我认为这非常重要,所以我会投票支持你。 - MGZero
1
@Bitmap:这个问题特别涉及内存资源。甚至在标题中都有说明。请指出这21页中哪一页提供了问题的答案。我找不到任何信息。因此,我选择了“-1此答案没有用”的选项。这与我的声望无关。 - MSalters
@MSalters 一旦你完成了对这个文档的处理,就可以得出哪些资源是内存资源,哪些不是的结论。 - Bitmap
@Bitmap:这假设了一个二元区分。资源要么是内存资源,要么不是内存资源。但问题已经有了连接对象的反例。(请注意,您的论文也没有解决这种混合情况) - MSalters
@MSalters 这份文档并不需要涉及数据库连接资源,以使其对这个问题至关重要。它清楚地阐述了明确回答问题的显式、概念和期望。我本可以直接回答“是”,但我想指出基础知识——一旦正确理解,就必不可少地得出结论。你可能对文件的标准有所了解,但这并不意味着它对答案来说太大而无法接受。 - Bitmap

-1
问题最好从另一个角度回答,我的看法是-“为什么我不需要手动释放内存”。
这引出了一个问题,“为什么我需要释放任何资源?”
基本上,您的运行程序使用许多形式的资源来执行和工作(CPU周期,内存位置,磁盘访问等)。几乎所有资源都遭受“稀缺性”的影响,即存在固定数量的任何此类资源可用,如果分配了所有资源,则操作系统无法满足请求,通常您的程序无法继续运行并且会非常不稳定- 可能导致整个系统不稳定。我想到的唯一一种不稀缺的资源是CPU周期,您可以发出尽可能多的指令,只受您发出指令的速率的限制,它们不像内存或文件句柄那样消耗。
因此,您使用的任何资源(内存,文件句柄,数据库连接,网络套接字等)都来自固定数量的资源(避免使用“池”一词),随着您的程序(以及其他程序和操作系统本身)分配这些资源,可用数量会减少。

如果一个程序请求并分配资源,但从未释放这些资源以供其他用途使用,最终(通常很快)系统将耗尽这些资源。此时,系统要么停止运行,要么有时可以强制终止有问题的程序。

在90年代之前,资源管理(至少在主流开发中)是每个程序员都必须明确处理的问题。一些资源分配管理并不太难,主要是因为分配已经抽象化了(例如文件句柄或网络套接字),当不再需要时,可以获取资源、使用它并显式释放它。

然而,管理内存非常困难,特别是因为内存分配不能在设计时(在非平凡情况下)计算,而数据库连接可以通过这种方式进行可行的管理。(无法知道将使用多少内存,很难/不可能知道何时不再使用内存分配)。此外,内存分配往往会持续一段时间,而大多数其他资源分配仅限于狭窄的范围,通常在单个try块、方法或最多通常在类中。因此,供应商开发了抽象内存分配的方法,并将其纳入由执行环境而不是程序处理的单个管理系统中。

这是托管环境(例如Java,.NET)和非托管环境(例如通过操作系统直接运行的C、C++)之间的区别。在C/C++中,内存分配是显式完成的(使用malloc()/new和相关的重新分配),这会导致各种问题-我需要多少内存?如何计算何时需要更多/更少?如何释放内存?如何确保我没有使用已经释放的内存?如何检测和管理内存分配请求失败的情况?如何避免写入内存(也许不是我的内存)?所有这些都非常困难,会导致内存泄漏,核心转储和各种半随机、不可重现的错误。

因此,Java实现了自动内存管理。程序员只需分配一个新对象,既不关心,也不应该关心内存分配的内容或位置(这也是为什么托管环境中没有太多指针的原因)。

object thing = new Object();

这就是需要做的所有事情。JVM将跟踪可用的内存,当它需要分配内存时,它会释放不再使用的内存,并提供处理内存不足情况的方法,以尽可能地优雅地限制任何问题(并将问题限制在执行线程/ JVM而不是整个操作系统)。

自动内存管理现在是大多数编程的标准,因为内存管理是迄今为止最难管理的资源(主要是因为其他资源已经在某种程度上被抽象化了,例如数据库连接池、套接字抽象等)。

所以,来回答这个问题,是的,你需要管理所有资源,但在Java中,你不需要(也不能)显式地管理内存(尽管在某些情况下值得考虑,例如设计缓存)。这样留下了所有其他需要显式管理的资源(这些都是非内存资源,即除对象实例化/销毁之外的所有内容)。

所有这些其他资源都包装在内存资源中,但这里并不是问题所在。例如,你只能打开有限数量的数据库连接,只能创建有限数量的文件句柄。你需要管理这些的分配。使用finally块可以确保在出现异常时释放资源。

例如

public void server()
{
  try
  {
    ServerSocket serverSocket = new ServerSocket(25);
  }
  catch (Exception exception)
  {
     // Something went wrong.
  }
  finally
  {
    // Clear up and deallocate the unmanaged resource serverSocket here.
    // The close method will internally ensure that the network socket is actually flushed, closed and network resources released.
    serverSocket.close();
    // The memory used by serverSocket will be automatically released by the JVM runtime at this point, as the serverSocket has gone out-of-scope- it is never used again, so can safely be deallocated.
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接