C ++削除とJava GC


8

Javaガベージコレクションは、ヒープ上のデッドオブジェクトを処理しますが、時々世界を凍結します。C ++ではdelete、作成したオブジェクトをそのライフサイクルの最後に破棄するために呼び出す必要があります。

これdeleteは、非凍結環境に支払うには非常に低価格のようです。関連deleteするすべてのキーワードを配置することは、機械的な作業です。新しいブランチが特定のオブジェクトを使用しなくなったら、コードを移動して削除を実行するスクリプトを作成できます。

では、ガベージコレクションのJavaビルドインとC ++ diyモデルの賛否両論は何ですか。


C ++対Javaスレッドを開始したくありません。私の質問は違います。
この全体的なGCのことは、「単純に、作成したオブジェクトを削除することを忘れないでください。専用のGCは必要ありません。それとも、「C ++でオブジェクトを破棄するのは本当にトリッキーです。私の時間の20%をそれに費やしますが、それでもメモリリークは一般的な場所です」


15
あなたの魔法のスクリプトが存在する可能性があるとは信じられません。何かを除いて、それは停止の問題に対する解決策を含まなければならないでしょう。あるいは、(そしてより可能性が高い)信じられないほど単純なプログラムでしか機能しない
Richard Tingle

1
また、現代のJavaは、大量のゴミが作成される極端な状況を除いてフリーズしません
Richard Tingle

1
@ThomasCarlisleは、パフォーマンスに影響を与える可能性があります。しかし、これらの場合には微調整するパラメーターがたくさんあり、時には別のgcに完全に切り替えることが解決策になる場合があります。それはすべて、利用可能なリソースの量と一般的な負荷に依存します。
ハルク

4
関連するすべての削除キーワードを配置することは機械的な作業です」-メモリリークを検出するツールがあるのはそのためです。それはとてもシンプルでエラーが起こりにくいからです。
JensG 2016年

2
@ Doval、RAIIを正しく使用している場合(これは非常に単純です)、簿記はほとんどありません。newメモリを管理するクラスのコンストラクタにdelete入り、デストラクタに入ります。そこからは、すべて自動です(ストレージ)。これは、ガベージコレクションとは異なり、メモリだけでなくすべてのタイプのリソースで機能することに注意してください。ミューテックスは、コンストラクタで取得され、デストラクタで解放されます。ファイルはコンストラクタで開かれ、デストラクタで閉じられます。
Rob K

回答:


18

C ++オブジェクトのライフサイクル

ローカルオブジェクトを作成する場合、それらを削除する必要はありません。コンパイラーは、オブジェクトがスコープ外になったときにそれらを自動的に削除するコードを生成します

オブジェクトポインターを使用して、フリーストア上にオブジェクトを作成する場合、(説明したように)不要になったオブジェクトを削除する必要があります。残念ながら、複雑なソフトウェアでは、これは見た目よりもはるかに難しいかもしれません(たとえば、例外が発生し、予期された削除部分に到達しない場合はどうなるでしょうか?)。

幸いなことに、最近のC ++(別名C ++ 11以降)では、などのスマートポインターがありますshared_ptr。作成されたオブジェクトを非常に効率的な方法で参照カウントします。Javaでガベージコレクターが行うのと少し似ています。そして、オブジェクトが参照されなくなるとすぐに、最後のアクティブshared_ptrがオブジェクトを削除します。自動的に。ガベージコレクターのようですが、一度に1つのオブジェクトが遅延なしに(Ok:いくつかの追加の注意が必要でweak_ptr、循環参照に対処する必要があります)

結論: 今日では、メモリ割り当てを気にすることなくC ++コードを記述できます。これは、GCと同じようにリークはありませんが、フリーズ効果はありません。

Javaオブジェクトのライフサイクル

良い点は、オブジェクトのライフサイクルを気にする必要がないことです。それらを作成するだけで、残りはJavaが処理します。現代のGCは、もはや(存在する場合など、必要なオブジェクトを識別し、破壊する循環参照死んでオブジェクト間を)。

残念ながら、この快適さのため、オブジェクトが実際に削除されるタイミングを実際に制御することはできません。意味的には、削除/破棄はガベージコレクションと一致します。

メモリの観点からのみオブジェクトを見る場合、これはまったく問題ありません。凍結を除いて、これらは致命的ではありません(人々はこれに取り組んでいます)。私はJavaの専門家ではありませんが、オブジェクトが不要になったにもかかわらず参照が誤って保持されるため、遅延の破壊によりJavaでのリークの特定難しくなると思います(つまり、オブジェクトの削除を実際に監視することはできません)。

しかし、オブジェクトがメモリ以外のリソース(オープンファイル、セマフォ、システムサービスなど)を制御する必要がある場合はどうでしょうか。クラスは、これらのリソースを解放するメソッドを提供する必要があります。また、リソースが不要になったときにこのメソッドが確実に呼び出されるようにする必要があります。コードを通るすべての可能な分岐パスで、例外の場合にも呼び出されるようにします。課題は、C ++での明示的な削除と非常によく似ています。

結論: GCはメモリ管理の問題を解決します。ただし、他のシステムリソースの管理については取り上げていません。「ジャストインタイム」の削除がないと、リソース管理が非常に難しくなる可能性があります。

削除、ガベージコレクション、およびRAII

オブジェクトの削除と、削除時に呼び出されるデストラクタを制御できる場合、RAIIを利用できます。このアプローチでは、メモリをリソース割り当ての特殊なケースとしてのみ見なし、リソース管理をオブジェクトのライフサイクルにより安全にリンクし、リソースの使用を厳密に制御します。


3
(現代の)ガベージコレクターの優れた点は、循環参照について考える必要がないことです。相互に到達できない場合に到達できないオブジェクトのグループは、検出および収集されます。これは、単純な参照カウント/スマートポインターよりも大きな利点です。
ハルク

6
+1「例外」の部分は十分に強調することができません。例外が存在すると、手動メモリ管理の困難で無意味なタスクが事実上不可能になるため、C ++では手動メモリ管理は使用されません。RAIIを使用します。newコンストラクター/スマートポインターの外では使用しないでくださいdelete。また、デストラクターの外では使用しないでください。
Felix Dombek 2016年

@ハルクここにポイントがあります!私の主張のほとんどはまだ有効ですが、GCは多くの進歩を遂げました。また、循環参照は、参照カウントのスマートポインタだけでは処理が実際に困難です。そこで、より良いバランスを保つために、それに応じて私の回答を編集しました。また、フリーズ効果を軽減するための可能な戦略についての記事への参照を追加しました。
クリストフ

3
+1オブジェクトの削除/ファイナライズがいつ発生するか(または発生するか)をきめ細かく制御できない場合、RAIIは機能しないため、メモリ以外のリソースの管理には、実際にGC言語の追加の労力が必要です。これらの言語のほとんどで特別な構成要素が利用可能であり(たとえば、javaのtry-with-resourcesを参照)、コンパイラの警告と組み合わせて、これらのことを正しく行うのに役立ちます。
ハルク

1
Like garbage collector, but one object at a time and without delay (Ok: you need some extra care and weak_ptr to cope with circular references).ただし、参照カウントはカスケードできます。たとえば、への最後の参照AはなくなりAますが、への最後の参照もあります。B最後の参照はC... And you'll have the responsibility to make sure that this method is called when the resources are no longer needed. In every possible branching path through your code, ensuring it is also invoked in case of exceptions.JavaとC#には、このための特別なブロックステートメントがあります。
Doval

5

この削除は、非凍結環境に支払うための非常に低価格のようです。関連するすべての削除キーワードを配置することは、機械的な作業です。新しいブランチが特定のオブジェクトを使用しなくなったら、コードを移動して削除を実行するスクリプトを記述できます。

そのようなスクリプトを書くことができたら、おめでとうございます。あなたは私よりも優れた開発者です。はるかに。

実際のケースでメモリリークを実際に回避できる唯一の方法は、オブジェクトの所有者である非常に厳密なルールを備えた非常に厳密なコーディング標準、またはオブジェクトを解放できる場合と解放する必要がある場合、またはオブジェクトへの参照をカウントして削除するスマートポインタなどのツールです。最後の参照がなくなったときのオブジェクト。


4
はい。C++にはRAIIがあるため、手動でリソース管理を行うと、常にエラーが発生しやすくなります。幸い、Javaでのみ必要です(非メモリリソースの場合)。
デデュプリケータ2016年

実際のケースでリソースリークを実際に回避できる唯一の方法は、オブジェクトの所有者である非常に厳しいルールを持つ非常に厳密なコーディング標準です...メモリだけがリソースではなく、ほとんどの場合、最も重要なリソースではありませんどちらか。
curiousguy

2
RAIIを「非常に厳密なコーディング標準」と見なす場合。私はそれを「成功の落とし穴」であり、ごく簡単に使用できると考えています。
Rob K

5

RAIIを使用して正しいC ++コードを記述する場合、通常、新規または削除を記述しません。作成する唯一の「新規」は共有ポインタ内にあるため、「削除」を使用する必要はありません。


1
new共有ポインターを使用std::make_sharedする場合でも、まったく使用しないでください。代わりに使用する必要があります。
ジュール

1
以上、make_unique。実際に所有権を共有する必要があるのは非常にまれです。
マルク

4

プログラマーの生活を楽にし、メモリリークを防ぐことは、ガベージコレクションの重要な利点ですが、それだけではありません。もう1つは、メモリの断片化を防ぐことです。C ++では、newキーワードを使用してオブジェクトを割り当てると、オブジェクトはメモリ内の固定位置に留まります。つまり、アプリケーションが実行されると、割り当てられたオブジェクト間に空きメモリのギャップが生じます。したがって、オペレーティングシステムはギャップの間に収まる特定のサイズの割り当てられていないブロックを見つける必要があるため、C ++でのメモリの割り当ては必然的により複雑なプロセスになるはずです。

ガベージコレクションは、削除されていないすべてのオブジェクトを取得し、それらをメモリ内でシフトして、連続したブロックを形成することによって処理します。ガベージコレクションに時間がかかる場合は、メモリの割り当て解除自体ではなく、おそらくこのプロセスが原因です。その利点は、メモリ割り当てに関しては、ポインタをスタックの最後に移動するのとほとんど同じくらい簡単なことです。

したがって、C ++ではオブジェクトの削除は高速ですが、オブジェクトの作成には時間がかかる場合があります。Javaでは、オブジェクトの作成に時間はかかりませんが、たまにハウスキーピングを行う必要があります。


4
はい、無料ストアからの割り当ては、C ++ではJavaよりも遅くなります。幸い、頻度ははるかに低く、独自の裁量で特殊なアロケーターを簡単に使用して、異常なパターンを見つけることができます。また、C ++ではすべてのリソースが同じですが、Javaには特殊なケースがあります。
デデュプリケータ2016年

3
C ++での20年間のコーディングで、メモリの断片化が問題になるのを見たことがありません。複数のキャッシュレベルを備えたプロセッサで仮想メモリ管理を備えた最新のOSでは、これを問題として大幅に排除しています。
Rob K

@RobK-「複数のキャッシュレベルを持つプロセッサで仮想メモリ管理を備えた最新のOSは、問題として[フラグメンテーション]を大幅に排除しました」-彼らはそうしていません。あなたはそれに気付かないかもしれませんが、それでも起こります、それでも(1)メモリの浪費と(2)キャッシュの効率の悪い使用を引き起こし、唯一の実行可能な解決策はGCをコピーするか、それを確実にするために手動のメモリ管理の非常に注意深い設計です起こらない。
ジュール

3

Javaの主な約束は

  1. 理解できるCのような構文
  2. どこにでも1つの実行を書き込む
  3. 私たちはあなたの仕事をより簡単にします-私たちはゴミの世話もします。

Javaはゴミが(必ずしも効率的な方法で)廃棄されないことを保証しているようです。C / C ++を使用する場合は、自由と責任の両方があります。あなたはJavaのGCよりもそれをうまくやることができます、またはあなたははるかに悪いことができます(deleteすべて一緒にスキップしてメモリリークの問題があります)。

「特定の品質基準を満たし」、「価格/品質比」を最適化するコードが必要な場合は、Javaを使用してください。ミッションクリティカルなアプリケーションのパフォーマンスを向上させるために追加のリソース(専門家の時間)を投資する準備ができている場合は、Cを使用してください。


まあ、すべての約束は簡単に破られたと見ることができます。
デデュプリケータ2016年

ことを除いて唯一のゴミJavaはコレクトメモリを動的に割り当てられます。他の動的に割り当てられたリソースについては何もしません。
Rob K

@RobK-それは厳密には真実ではありません。オブジェクトのファイナライザの使用ができる他のリソースの割り当て解除を処理します。ほとんどの場合それは望まないので(メモリとは異なり、他のほとんどのリソースタイプははるかに制約されているか、さらには一意であるため、正確な割り当て解除が重要です)、これは広く推奨されませんが、実行できます。Javaには、他のリソースの管理を自動化するために使用できるtry-with-resourcesステートメントもあります(RAIIと同様の利点があります)。
ジュール

@ジュール:1つの重要な違いは、C ++オブジェクトでは自己破壊することです。Javaでは、Closeableオブジェクトは自分自身を閉じることができません。「new Something(...)」または(悪い)Something s = SomeFactory.create(...)を書くJava開発者は、SomethingがCloseableかどうかを確認する必要があります。クラスをnot CloseableからCloseableに変更することは、最悪の種類の重大な変更であり、実際には決して実行できません。クラスレベルではそれほど悪くありませんが、Javaインターフェースを定義するときに深刻な問題が発生します。
ケビンcline

2

ガベージコレクションによる大きな違いは、オブジェクトを明示的に削除する必要がないことではありません。はるかに大きな違いは、オブジェクトをコピーする必要がないことです。

これには、一般にプログラムとインターフェースの設計に浸透する効果があります。これがどれほど広範囲に及ぶかを示すために、ほんの小さな例を挙げましょう。

Javaでは、スタックから何かをポップすると、ポップされている値が返されるため、次のようなコードを取得します。

WhateverType value = myStack.Pop();

Javaでは、これは例外的に安全です。なぜなら、私たちが実際に行っているのは、オブジェクトへの参照をコピーすることだけであり、例外なしで発生することが保証されているからです。同じことはC ++では当てはまりません。C ++では、値を返すとは、その値をコピーすることを意味し(少なくとも意味することもあります)、一部のタイプでは例外をスローする可能性があります。アイテムがスタックから削除された後、コピーがレシーバーに到達する前に例外がスローされた場合、アイテムはリークしています。それを防ぐために、C ++のスタックはやや不格好なアプローチを使用しており、トップアイテムの取得とトップアイテムの削除は2つの別々の操作です。

WhateverType value = myStack.top();
myStack.pop();

最初のステートメントが例外をスローした場合、2番目のステートメントは実行されないため、コピー中に例外がスローされた場合、アイテムは何も起こらなかったかのようにスタックに残ります。

明らかな問題は、これは単に不器用であり、(それを使用したことがない人にとって)予期しないことです。

同じことがC ++の他の多くの部分にも当てはまります。特に一般的なコードでは、例外の安全性は設計の多くの部分に行き渡っています。 Javaは既存のオブジェクトへの新しい参照を作成するだけです(これはスローできないため、例外を心配する必要はありません)。

限り挿入する簡単なスクリプトとしてdelete必要な場所:あなたは、静的ソースコードの構造に基づいてアイテムを削除する時期を決定することができた場合、それはおそらく使用されていてはいけませんnewし、delete最初の場所で。

これがほぼ確実に不可能であるプログラムの例を挙げましょう。電話をかける、追跡する、請求するなどのシステムです。電話をかけると、「call」オブジェクトが作成されます。呼び出しオブジェクトは、適切なレコードを請求ログに追加するために、誰に電話したか、どれくらい通話したかなどを追跡します。呼び出しオブジェクトはハードウェアの状態を監視するため、電話を切ると、(広く議論されているを使用してdelete this;)自分自身を破壊します。ただ、「電話を切ったとき」ほど簡単ではありません。たとえば、電話会議を開始して2人を接続し、電話を切る場合がありますが、電話を切った後でも、これらの2者間で通話は継続されます(ただし、請求が変更される場合があります)。


「アイテムがスタックから削除された後、コピーがレシーバーに到達する前に例外がスローされた場合、アイテムはリークされています」これに関する参照はありますか?私はc ++の専門家ではありませんが、これは非常に奇妙に聞こえるからです。
Esben Skov Pedersen

@EsbenSkovPedersen:GoTW#8が妥当な出発点になります。アクセスできる場合は、Exceptional C ++にはさらに多くの機能があります。どちらも、少なくともC ++に関するいくつかの既存の知識を期待していることに注意してください。
Jerry Coffin 2016年

それは十分に簡単なようです。「C ++では、値を返すということは、その値をコピーすることを意味し、少なくとも例外をスローする可能性があるいくつかの型を含む」という混乱を招いたのは、この文です。このコピーはヒープまたはスタックにありますか?
Esben Skov Pedersen

あなたは絶対にしないでください、あなたがしたい場合は、あなたがしたいときにオブジェクトをコピーすることPITA作るごみ収集の言語(Javaの、C#)とは異なり、C ++でオブジェクトをコピーする必要がありますが、次のことができます。私が作成するオブジェクトの90%は、スコープから外れると破棄され、リソースが解放されます。オブジェクトの10%はせいぜいばかげているように見える必要があるため、すべてのオブジェクトを強制的に動的ストレージに入れる。
Rob K

2
ただし、これはガベージコレクションの使用による違いではありませんが、Javaの単純化された「すべてのオブジェクトは参照である」という哲学によるものです。反例としてC#を検討してください。これガベージコレクションされた言語ですが、コピーセマンティクスを持つ値オブジェクト(ローカル用語では「構造体」で、C ++構造体とは異なります)もあります。C#は、(1)参照型と値型を明確に分離し、(2)ユーザーコードではなくバイト単位のコピーを使用して常に値型をコピーすることで問題を回避し、コピー中の例外を防ぎます。
Jules

2

ここで言及されていないのは、ガベージコレクションから得られる効率があるということです。最も一般的に使用されるJavaコレクターでは、オブジェクトが割り当てられる主な場所は、コピーコレクター用に予約された領域です。物事が始まるとき、このスペースは空です。オブジェクトが作成されると、残りの隣接するスペースにオブジェクトを割り当てることができなくなるまで、大きなオープンスペースで互いに隣り合って割り当てられます。GCが起動し、このスペースで死んでいないオブジェクトを探します。ライブオブジェクトを別の領域にコピーし、それらをまとめます(つまり、断片化しません)。古いスペースはクリーンと見なされます。次に、オブジェクトを緊密に一緒に割り当て続け、必要に応じてこのプロセスを繰り返します。

これには2つの利点があります。1つ目は、未使用のオブジェクトの削除に時間を費やす必要がないことです。ライブオブジェクトがコピーされると、スレートはクリーンであると見なされ、デッドオブジェクトは単に忘れられます。多くのアプリケーションでは、ほとんどのオブジェクトはあまり長く存続しないため、ライブセットをコピーするコストは、デッドセットを気にする必要がないことによって得られる節約と比較して安価です。

2番目の利点は、新しいオブジェクトが割り当てられたときに、隣接する領域を検索する必要がないことです。VMは常に次のオブジェクトが配置される場所を認識しています(警告:同時実行を無視して単純化)。

この種の収集と割り当ては非常に高速です。全体的なスループットの観点から見ると、多くのシナリオで勝つことは困難です。問題は、一部のオブジェクトが、コピーし続けるよりも長く存続することです。最終的には、コレクターがたまにかなりの時間一時停止する必要がある場合があり、いつ発生するかは予測できません。一時停止の長さとアプリケーションの種類によっては、これが問題になる場合と問題にならない場合があります。少なくとも1つの無停止コレクターがあります。一時停止のない性質を得るために、効率が低下するトレードオフがあると思いますが、その会社(Gil Tene)を設立した人の1人(GC)は、GCの専門家であり、彼のプレゼンテーションはGCに関する素晴らしい情報源です。


Azulは、GCの連中、コンパイラの連中、パフォーマンスの連中、およびSunの元のJava CPUの連中によって設立されました。彼らは何をしているのか知っています。
イェルクWミッターク

@JörgWMittagは、複数の創設者がいたことを反映するように更新されました。ありがとう
JimmyJames

1

それとも、「C ++でオブジェクトを破棄するのは非常に難しい-私はそれに20%の時間を費やしていますが、メモリリークはよくある場所です」というようなものでしょうか。

C ++やCでの私の個人的な経験では、メモリリークは回避するための大きな苦労ではありませんでした。たとえば、適切なテスト手順とValgrindを使用するとoperator new/malloc、対応なしのへの呼び出しによって引き起こされた物理的なリークdelete/freeは、多くの場合、迅速に検出および修正されます。公平を期すために、大規模なCまたは古い学校のC ++コードベースにはdeleting/freeing、テストのレーダーの下で飛んだエッジケースに存在しない結果として、物理的に数バイトのメモリリークがある物理的にあいまいなエッジケースがある可能性があります。

しかし、実際的な観察としては、私が遭遇する最も漏れやすいアプリケーション(作業しているデータ量が増えていなくても、実行時間が長くなるほど多くのメモリを消費するアプリケーションなど)は、通常CまたはC ++。LinuxカーネルやUnreal Engine、さらにはJavaの実装に使用されるネイティブコードのようなものは、遭遇するリークの多いソフトウェアのリストの中に見つかりません。

私が遭遇する傾向のある最も有名な種類の漏出ソフトウェアは、ガベージコレクションを使用しているにもかかわらず、FlashゲームなどのFlashアプレットのようなものです。そして、これから何かを推測するのであれば、それは公平な比較ではありません。多くのFlashアプリケーションは、健全なエンジニアリングの原則とテスト手順を欠いている新進の開発者によって書かれているためです(同様に、GCを扱う熟練した専門家がいると確信しています)。リーキーなソフトウェアに苦労しないでください)、しかし、GCがリーキーなソフトウェアの作成を阻止していると考える人には、言いたいことがたくさんあります。

ぶら下がりポインタ

私の特定のドメイン、経験、そして主にCとC ++を使用しているように(そして、GCの利点は私たちの経験とニーズによって変わると思います)、GCが私のために解決する最も直接的なことは実用的なメモリリークの問題ではありませんが、ぶら下がりポインタアクセス。これは、文字どおり、ミッションクリティカルなシナリオでの命の恩人になる可能性があります。

残念ながら、GCが未解決のポインターアクセスを解決する多くの場合、GCは同じ種類のプログラマーのミスを論理メモリリークに置き換えます。

新登場のコーダーによって作成されたFlashゲームを想像すると、彼はゲーム要素への参照を複数のデータ構造に格納し、それらがこれらのゲームリソースの所有権を共有するようにする可能性があります。残念ながら、次のステージに進むときにデータ構造の1つからゲーム要素を削除するのを忘れて、ゲーム全体がシャットダウンされるまで解放されないという間違いを犯したとします。ただし、要素が描画されていないか、ユーザーの操作に影響を与えていないため、ゲームはまだ正常に動作しているように見えます。それにもかかわらず、フレームレートがスライドショーに対応している間、ゲームはますます多くのメモリを使用し始めますが、隠された処理は、ゲーム内のこの隠された要素のコレクションをループします(現在、爆発的なサイズになっています)。これは、私がこのようなFlashゲームで頻繁に遭遇する問題です。

  • アプリケーションを閉じるときにメモリが解放されているため、これは「メモリリーク」とは見なされず、代わりに「スペースリーク」またはこの効果の何かと呼ばれる可能性があるという人に遭遇しました。このような区別は問題を特定して話すのに役立つかもしれませんが、対処するときに「メモリリーク」ほど問題ではないように話している場合、このような区別はこのコンテキストではそれほど役立ちません。ソフトウェアを確保するという実際的な目標は、実行時間が長くなってもばかげた量のメモリを占有しないことです(プロセスの終了時にプロセスのメモリを解放しないあいまいなオペレーティングシステムについて話しているのでない限り)。

今、同じ新進の開発者がC ++でゲームを書いたとしましょう。その場合、通常、ゲームにはメモリを「所有」する中心的なデータ構造が1つだけありますが、他のメモリはそのデータを指し示します。彼が同じ種類の間違いを犯した場合、次のステージに進むときに、ぶら下がりポインタにアクセスした結果としてゲームがクラッシュする可能性があります(さらに悪いことに、クラッシュ以外の何かを行います)。

これは、私のドメインでGCとGCなしの間で最も頻繁に遭遇する傾向がある最も直接的な種類のトレードオフです。私のドメインでは、実際にはGCをあまり気にしていません。これは、ミッションクリティカルではありません。以前のチームでGCを無計画に使用して、上記のようなリークを引き起こしていたためです。 。

私の特定のドメインでは、多くの場合、ソフトウェアがクラッシュまたはグリッチアウトすることを実際に好みます。なぜなら、ソフトウェアが30分実行された後、ソフトウェアが不思議なことに大量のメモリを消費している理由を追跡するよりも、検出が少なくともはるかに簡単だからです。単体テストと統合テストは問題なく成功しました(メモリはシャットダウン時にGCによって解放されているため、Valgrindからでも送信されません)。それでも、それは私の側でのGCの非難や、役に立たないまたはそのようなものであると言う試みではありませんが、私がリークしたソフトウェア(逆に、GCを利用する1つのコードベースが、これまでで最もリークの多いリークであるという反対の経験をしました。そのチームの多くのメンバーが公平であるために、弱い参照が何であるかさえ知りませんでした、

共有の所有権と心理学

私が見つけたガベージコレクションで「メモリリーク」が発生しやすくなる問題(そして、「スペースリーク」はユーザーエンドの観点からはまったく同じように動作するように呼びます)の手に注意して使用しない人は、私の経験ではある程度「人間の傾向」に関係しています。そのチームと私が今まで遭遇した最も漏洩しやすいコードベースの問題は、GCがリソースの所有者について考えるのを止めさせるという印象を彼らが受けているように見えることでした。

私たちのケースでは、お互いを参照するオブジェクトが非常に多くありました。モデルは、マテリアルライブラリとシェーダーシステムとともにマテリアルを参照します。マテリアルは、テクスチャライブラリと特定のシェーダーと共にテクスチャを参照します。カメラは、レンダリングから除外する必要があるあらゆる種類のシーンエンティティへの参照を保存します。リストは無期限に続くようでした。これにより、システムの多額のリソースが所有され、アプリケーションの状態の他の10か所以上で一度に延長され、リークにつながるような人為的エラーが非常に発生しやすくなりました(そしてマイナーな問題ですが、私はギガバイト単位で話しており、深刻なユーザビリティの問題があります)。概念的には、これらのすべてのリソースを所有権で共有する必要はなく、概念的にはすべて1人の所有者がいました。

誰がどのメモリを所有しているかについて考えるのをやめ、幸いにも、これについて考えずにオブジェクトへの寿命を延ばす参照をあちこちに保存すると、ポインタがぶら下がってもソフトウェアはクラッシュしませんが、そのような状況下ではほぼ間違いなく不注意な考え方で、追跡が非常に難しく、テストを回避するような方法で、狂ったようにメモリをリークし始めます。

私のドメイン内のぶら下がりポインタに1つの実用的な利点がある場合、それは非常に厄介な不具合やクラッシュを引き起こすことです。そして、少なくとも信頼性のあるものを出荷したい場合、開発者はリソース管理について考え始め、概念的に不要になったオブジェクトへのすべての追加の参照/ポインターを削除するために必要な適切なことを行うようにインセンティブを与える傾向があります。

アプリケーションリソース管理

適切なリソース管理は、リークが深刻なフレームレートと使いやすさの問題を引き起こす永続的な状態が保存されている状態で、長期間有効なアプリケーションでのリークの回避について話している場合のゲームの名前です。そして、ここでリソースを正しく管理することは、GCの有無にかかわらず、同様に困難です。これらの作業は、オブジェクトがポインタであれ、存続期間を延長する参照であれ、不要になったオブジェクトへの適切な参照を削除するためのどちらかの方法であり、手動によるものです。

それは、私deleteたちのことを忘れないで、私の領域での課題newです(私たちが粗雑なテスト、実践、およびツールでアマチュア時間を話しているのでない限り)。そして、GCを使用しているかどうかを検討する必要があります。

マルチスレッド

ドメインで非常に慎重に使用できる場合、GCで非常に役立つと思われるもう1つの問題は、マルチスレッドコンテキストでのリソース管理を簡略化することです。リソースへの存続期間を延長する参照をアプリケーション状態の複数の場所に保存しないように注意する場合、GC参照の存続期間を延長する性質は、拡張するためにアクセスされるリソースをスレッドが一時的に拡張する方法として非常に役立つ可能性があります。スレッドが処理を完了するために必要な、ほんの短い期間の寿命。

この方法でGCを非常に注意深く使用すると、リークのない非常に正確なソフトウェアが得られると同時に、マルチスレッド化が簡素化されると思います。

GCがない場合でも、これを回避する方法はあります。私の場合、ソフトウェアのシーンエンティティ表現を統合します。クリーンアップフェーズの前に、かなり一般化された方法で一時的にシーンリソースを一時的に拡張するスレッドを使用します。これはGCのように少しにおいがするかもしれませんが、「共有所有権」が関係しておらず、スレッド内の均一なシーン処理設計のみが上記のリソースの破棄を遅らせる点が異なります。それでも、このようなマルチスレッドの場合に、良心的な開発者と非常に慎重に使用でき、関連する永続領域で弱参照を使用する場合は、GCに依存する方がはるかに簡単です。

C ++

最終的に:

C ++では、削除されたオブジェクトをそのライフサイクルの最後に破棄するために、deleteを呼び出す必要があります。

最近のC ++では、これは通常、手動で行うべきことではありません。それをすることを忘れることはそれほどでもありません。画像に例外処理を含める場合、対応するdelete以下の呼び出しを書き込んでも、コンパイラが挿入した自動デストラクタ呼び出しに依存していないとnew、何かが途中でスローされてdelete呼び出しに到達しない可能性があります。君は。

C ++では、例外をオフにして、意図的にスローしないようにプログラムされた特別なライブラリを使用して埋め込みコンテキストのように作業している場合を除き、このような手動のリソースクリーンアップ(dtorの外部でmutexをロック解除するための手動呼び出しの回避を含む)を行わない限り、実際に必要ですたとえば、メモリの割り当て解除だけではありません)。例外処理ではほとんどの場合それが要求されるため、ほとんどの場合、すべてのリソースのクリーンアップはデストラクタを介して自動化する必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.