.NETのMemoryStreamが閉じられていない場合、メモリリークが発生しますか?
-
04-07-2019 - |
質問
次のコードがあります:
MemoryStream foo(){
MemoryStream ms = new MemoryStream();
// write stuff to ms
return ms;
}
void bar(){
MemoryStream ms2 = foo();
// do stuff with ms2
return;
}
割り当てたMemoryStreamが後で何らかの方法で破棄されない可能性はありますか?
これを手動で閉じると主張するピアレビューがありますが、有効なポイントがあるかどうかを知るための情報が見つかりません。
解決
何かが使い捨てである場合は、常に廃棄する必要があります。 ms(2)が破棄されるようにするには、bar()メソッドでusingステートメントを使用する必要があります。
最終的にはガベージコレクターによってクリーンアップされますが、常に破棄することをお勧めします。コードでFxCopを実行すると、警告としてフラグが立てられます。
他のヒント
少なくとも現在の実装では、何もリークしません。
Disposeを呼び出しても、MemoryStreamが使用するメモリはより速くクリーンアップされません。呼び出し後の読み取り/書き込み呼び出しでストリームが実行可能になるのを 停止します。これはあなたにとって有用な場合とそうでない場合があります。
MemoryStreamから別の種類のストリームに移動することを絶対に絶対に確信している場合、Disposeを呼び出さなくても害はありません。ただし、別のストリームを使用するように変更した場合、簡単な方法を早期に選択したため、見つけにくいバグに噛まれたくないため、一般的には良い習慣です。に。 (一方で、YAGNI引数があります...)
とにかくそれを行うもう1つの理由は、新しい実装により、廃棄時に解放されるリソースが 導入される可能性があることです。
はい、リークの定義方法と意味する量に応じて、 a リークがあります ...
リークにより、「メモリは割り当てられたままであり、使用したとしても使用できない」ことを意味する場合後者では、disposeを呼び出した後はいつでも意味があり、そうではありませんが、リークがあるかもしれませんが、永続的ではありません(つまり、アプリケーションランタイムの寿命)。
MemoryStreamが使用する管理メモリを解放するには、参照を無効にすることで参照を解除する必要があります。これにより、ガベージコレクションの対象になります。これに失敗すると、使用中にメモリが割り当てられなくなるため、使用が終了してから参照が範囲外になるまで一時的なリークが発生します。
usingステートメントの利点は(単にdisposeを呼び出すことよりも)、usingステートメントで参照を宣言できることです。 usingステートメントが終了すると、disposeが呼び出されるだけでなく、参照がスコープ外になり、参照を事実上無効にして、オブジェクトを" reference = null"の記述を忘れずにガベージコレクションの対象にします。コード。
すぐに何かを参照解除しないと、古典的な「永続的」ではありません。メモリリーク、それは間違いなく同じ効果があります。たとえば、(disposeを呼び出した後でも)MemoryStreamへの参照を保持し、メソッドの少し下にさらにメモリを割り当てようとすると、まだ参照されているメモリストリームで使用されているメモリが使用できなくなります。破棄を呼び出して使用し終わったとしても、参照を無効にするか、範囲外になるまで、あなたに。
これはすでに回答されていますが、情報隠蔽の昔ながらの原則により、将来的にはリファクタリングが必要になる可能性があることを付け加えます。
MemoryStream foo()
{
MemoryStream ms = new MemoryStream();
// write stuff to ms
return ms;
}
to:
Stream foo()
{
...
}
これは、呼び出し元がどのような種類のStreamが返されるかを気にするべきではないことを強調し、内部実装を変更することを可能にします(たとえば、単体テストのためにモックを作成する場合)。
バーの実装でDisposeを使用していない場合は、問題が発生する必要があります。
void bar()
{
using (Stream s = foo())
{
// do stuff with s
return;
}
}
すべてのストリームはIDisposableを実装します。 usingステートメントでメモリストリームをラップすると、元気になります。 usingブロックは、ストリームが何であれ閉じられ、破棄されることを保証します。
Fooを呼び出す場所ならどこでも(MemoryStream ms = foo())を使用できますが、それでも大丈夫だと思います。
.Dispose()
を呼び出す(または Using
でラップする)必要はありません。
.Dispose()
を呼び出す理由は、できるだけ早くリソースを解放するためです。
たとえば、限られたメモリセットと数千のリクエストが着信するStack Overflowサーバーの観点で考えてみてください。そのため、新しい着信要求に使用できます。
メモリをリークすることはありませんが、コードレビューアーは、ストリームを閉じる必要があることを示しています。そうするのは丁寧です。
メモリをリークする可能性がある唯一の状況は、ストリームへの参照を誤って残し、決して閉じない場合です。あなたはまだ実際にメモリをリークしていませんが、あなたがそれを使用していると主張する時間を不必要に延長している 。
主に一貫性のために、 using
ステートメントの bar()
でMemoryStreamをラップすることをお勧めします。
- 現在、MemoryStreamは
.Dispose()
のメモリを解放しませんが、将来のある時点で、またはあなた(またはあなたの会社の誰か)がそれを置き換える可能性があります独自のカスタムMemoryStreamなどを使用します。 - プロジェクトにパターンを確立して、ストリームがすべて破棄されるようにするのに役立ちます。「すべてのストリームを破棄する必要がある」と言うことで、より明確に線が引かれます。 「一部のストリームは破棄する必要がありますが、特定のストリームを破棄する必要はありません」...
- 他のタイプのストリームを返すことができるようにコードを変更した場合、とにかく破棄するように変更する必要があります。
IDisposableを作成して返すときに foo()
のような場合に私が通常行うもう1つのことは、オブジェクトの構築と return
の間の障害を確実にキャッチすることです。例外、オブジェクトの破棄、および例外の再スロー:
MemoryStream x = new MemoryStream();
try
{
// ... other code goes here ...
return x;
}
catch
{
// "other code" failed, dispose the stream before throwing out the Exception
x.Dispose();
throw;
}
オブジェクトがIDisposableを実装する場合、完了したら.Disposeメソッドを呼び出す必要があります。
一部のオブジェクトでは、DisposeはCloseと同じことを意味し、その逆も同様です。その場合、どちらでもよいです。
今、あなたの特定の質問に対して、いいえ、あなたはメモリをリークしません。
私は.netの専門家ではありませんが、おそらくここの問題はリソース、つまりファイルハンドルであり、メモリではありません。ガベージコレクターは最終的にストリームを解放し、ハンドルを閉じると思いますが、コンテンツをディスクに確実にフラッシュするために、明示的に閉じることは常にベストプラクティスだと思います。
ガベージコレクション言語では、管理されていないリソースの処理は非決定的です。 Disposeを明示的に呼び出した場合でも、バッキングメモリが実際に解放されるタイミングを完全に制御することはできません。オブジェクトがスコープ外に出ると、usingステートメントを終了するか、下位メソッドからコールスタックをポップアップするかに関わらず、Disposeは暗黙的に呼び出されます。これはすべて言われていますが、オブジェクトは実際には管理対象リソース(ファイルなど)のラッパーである場合があります。これが、finallyステートメントを明示的に閉じるか、usingステートメントを使用することをお勧めする理由です。 乾杯
MemorySteramは、管理オブジェクトであるバイトの配列に他なりません。
これを破棄したり閉じたりすることを忘れても、ファイナライズのオーバーヘッド以外の副作用はありません。
リフレクタでMemoryStreamのconstuctorまたはflushメソッドをチェックするだけで、良いプラクティスに従うだけでなく、それを閉じたり破棄したりする必要がない理由が明らかになります。