2012-03-24 53 views
4

我有客户端/服务器应用程序,其中客户端应用程序将打开文件。这些文件被分成大块,并发送到服务器。如何设计:随机访问文件时避免资源泄漏

不仅客户端发送文件块,而且还发送其他数据。每条消息(数据或文件chunk)都有一个优先级。

根据消息的优先级将所有消息缓存并写入TCP流。 当文件完全发送或接收时,当前输入和输出流将在双方(客户机/服务器)上关闭。这意味着流只要发送文件就保持打开状态。

TCP连接被允许失败,因为它将重新连接并且消息发送将被恢复,因此,这些流将在某个点被关闭。

但是,如果和当JVM将被杀死,例如,流不会被清理。

我首先想到解决这个问题的方法是在终结器中添加清理代码,但我明白当JVM被杀死(或者如果调用System.exit)时,这些可能无法运行。

我的第二个想法是重写应用程序的一部分,只需要使用流就可以读取/写入一个块。因此,我最终会打开/关闭文件的次数与块的次数相同。这种方法的优点是允许我使用try-catch-finally构造,但是我有直觉感觉开放和关闭文件,这往往意味着一定的开销。

那么当设计不允许最后{}块时如何清理资源?或者应该避免这样的设计,可能与我所描述的方式类似?

我还担心可能有多少文件打开,因为有优先级(理论上这是无限的)。

大多数文件通常是几个KiB的大,但在某些情况下,它们可能会达到几GB的大。

在此先感谢您的意见。

编辑:添加图像

File transfer as it is

+0

也许我并不清楚:该流我指的是的FileInputStream/FileOutputStreams。另一方面,TCP连接永远保持联机状态(如果失败,则会发生重新连接)。 – 2012-03-24 13:33:26

+0

我不确定我了解你的问题。通过清理流,你的意思是删除部分转移的文件? – WilQu 2012-03-24 13:39:45

+0

不,我的意思是在不同文件的FileInputStream/FileOutputStream上调用close()。我会尽量画出一些东西来减少这个问题的含义。几分钟后我会回来修改。 – 2012-03-24 13:47:17

回答

2

如果我正确地理解了这个问题,您担心在JVM以不受控制的方式终止的情况下无法正确清理。

虽然这是一个普遍问题,但在您的特殊情况下,我认为没有问题需要担心。你只打开你的文件阅读,所以没有持久的状态,你的应用程序可能会中断(据我所知,TCP连接的另一端可以正常处理断开连接)。正在打开的文件是应用程序内部的一种状态。如果该应用程序被终止,则操作系统负责清理所有锁或其可能在内部用于处理该文件操作的任何数据结构。这意味着在操作系统内核中不会留下“垃圾”,虽然它既不是一种优雅也不是一种推荐的清理方式,它只是起作用(当然,这只能用于紧急情况,而不能用作正常方式处理事物)。杀死你的应用程序将自动关闭打开的文件,不应该使文件进入任何不一致的状态。

当然,如果应用程序被杀害,它不会完成它正在执行的任何操作。这可能会导致不一致的应用程序级状态或某些类型的应用程序逻辑的其他问题,但仍然无法伤害任何OS级别的staructures(假设操作系统不是bug)。您可能会丢失数据或破坏应用程序的状态,但不应该破坏内核中的文件系统或数据。

所以,如果你杀了一个你有交易风格操作的应用程序(你想完全执行或根本不执行,但没有中间状态应该对外界可见的应用程序)。一个例子是如果你有一个文件,你需要用一个更新的版本来替换它。如果您首先截断旧文件并向其写入新内容(这是显而易见的方式),但如果您的应用程序在截断该文件后但在写入新内容之前被杀死,则会出现问题。但是,为了激发这种风险,你需要可变状态,即写一些东西。如果你只是读东西,你几乎肯定是安全的。

如果你遇到这种情况,你可以采取几种途径。一个是试图使应用程序防弹,并确保它总是很好地清理。在实践中,这是非常困难的。您可以将关闭挂钩添加到Java应用程序中,该程序将在应用程序关闭时执行,但它仅适用于受控关闭(如Linux上的常规kill(SIGTERM))。但是,这并不能保护你免受应用程序的强制性攻击(Linux上的kill -9),OOM杀手(也是Linux)等等。当然,其他操作系统也有这些情况的等同物或其他情况应用程序以无法控制的方式关闭。如果您未编写在受控硬件和软件环境中运行的实时应用程序,几乎不可能阻止应用程序被强制终止并阻止其执行其清理过程的所有方式。因此,合理的折中方案通常只在应用程序中采取简单的预防措施(如关机挂钩),但要记住,您无法阻止所有事情,因此无法进行手动清理。例如,覆盖文件的解决方案是将操作拆分为首先将旧文件移动到新名称以保留作为备份(此操作通常在操作系统级原子级,因此是安全的),然后写入将新文件的内容更改为旧名称,然后在检查新文件被正确写入后删除备份。这样,如果应用程序在操作之间被终止,则存在一个简单的清理过程:将备份文件移动到原始名称,从而恢复到较旧但一致的状态。这可以手动完成(但应该记录),或者您可以添加一个清理脚本或一个特殊的“清理”命令到您的应用程序,以使此操作简单,可见并消除过程中出现人为错误的可能性。假设你的应用程序只是很少被杀死,这通常比花费大量时间试图让应用程序防弹,以至于意识到它不是真的可行。拥抱失败往往比试图阻止它(不仅在编程中)更好。

你仍然可以得到在操作系统和硬件水平烧毁,但是这真的很难防止商品的硬件和操作系统。即使您的应用程序将新文件看到的位置正确,但它可能没有实际写入磁盘,并且它只驻留在缓存中,但如果您的应用程序被杀死,它不会丢失,但如果有人拉动电源,则会丢失插在机器上。但处理这种失败是一个完全不同的故事。

长话短说,你的具体情况下,如果只读取文件,操作系统应该如果您的应用程序被杀害,以及其他情况下的一些技巧上面提到执行所有的清理工作。

+0

你的话有很多的智慧。在应用程序/业务逻辑层面,任何事情都可能会失败,正如你所建议的那样。 – 2012-03-25 00:16:00

+0

我会考虑添加关闭挂钩,谢谢你的提示! 有人确认我不会伤害操作系统及其文件描述符/句柄,这是一种解脱。 但我不得不提到我*不要*有开写(通信是双向的),我知道这是不是从我的岗位明确的文件,所以我接受这是正确的答案。 你知道,如果有许多文件句柄打开很长一段时间会导致性能漏?我相信在操作系统级别文件句柄的数量是有限的。 – 2012-03-25 00:21:35

+0

而且是完整的,我要补充一点,我期待到这一点,因为我在这里所描述的错误的牺牲品(http://stackoverflow.com/questions/2614774/what-c​​an-cause-java-to-keep-running - 系统退出)在Win2008R2上。解决方案建议那里没有工作。 – 2012-03-25 00:26:21

1

看一看java.lang.Runtime.addShutdownHook()。我会尝试添加一个关闭所有打开的流的钩子,在这种情况下你必须维护它。但是,请注意:

在极少数情况下,虚拟机可能会中止,即停止运行而不会干净地关闭。当虚拟机在外部终止时发生这种情况,例如Unix上的SIGKILL信号或Microsoft Windows上的TerminateProcess调用。如果本机方法出错,例如破坏内部数据结构或尝试访问不存在的内存,则虚拟机也可能中止。如果虚拟机中止,则不能保证是否将运行任何关闭挂钩。

+0

谢谢,我会看看它! – 2012-03-25 00:23:46