web-dev-qa-db-ja.com

C ++削除vs Java GC

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

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

したがって、JavaビルドvsガベージコレクションのC++ diyモデルの利点と欠点は何ですか?.


C++ vs Javaスレッドを開始したくありません。私の質問は異なります。
GC全体-要約すると、「きちんと作成して、作成したオブジェクトを削除することを忘れないでください。専用のGCは必要ありませんか?それとも、「C++でオブジェクトを破棄する」のようなものですか?本当にトリッキーです。時間の20%を費やしていますが、メモリリークはよくあることです」

8
sixtytrees

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

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

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

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

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

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

良い点は、オブジェクトのライフサイクルを気にする必要がないことです。それらを作成するだけで、Javaが残りの処理を行います。 modern GC は、不要になったオブジェクトを特定して破棄します(存在する場合も含みます 循環参照 死んだオブジェクトの間)。

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

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

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

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

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

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

18
Christophe

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

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

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

5
gnasher729

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

5
Nikko

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

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

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

4
kamilk

Javaの主な約束は

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

Javaのように見えますが、ガベージが破棄されることが保証されます(必ずしも効率的な方法であるとは限りません)。C/ C++を使用すると、自由と責任の両方が得られます。JavaのGCよりも優れています。または、さらに悪い場合もあります(deleteをすべて一緒にスキップすると、メモリリークの問題が発生します)。

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

3
Ju Shua

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

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

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

WhateverType value = myStack.Pop();

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

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

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

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

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

必要な場所にdeleteを挿入する単純なスクリプトに関しては、ソースコードの構造に基づいてアイテムをいつ削除するかを静的に決定できる場合、おそらくnewを使用するべきではありませんでした。そもそもdeleteです。

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

2
Jerry Coffin

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

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

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

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

2
JimmyJames

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

C++やCでの私の個人的な経験では、メモリリークは回避するための大きな苦労ではありませんでした。たとえば、適切なテスト手順とValgrindを使用すると、対応するoperator new/mallocなしでdelete/freeを呼び出すことによって引き起こされる物理的なリークは、多くの場合迅速に検出され、修正されます。公平にするために、一部の大規模なCまたは古いC++コードベースでは、エッジのケースでdeleting/freeingが行われなかった結果、物理的に数バイトのメモリリークが発生するあいまいなエッジのケースが非常に現実的に発生する場合があります。テスト。

しかし、実際的な観察としては、私が遭遇する最も漏れやすいアプリケーション(作業しているデータ量が増えていなくても、実行時間が長くなるほどメモリを消費するアプリケーションなど)は、通常CまたはC++。 Linuxカーネルやアンリアルエンジン、あるいは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人の所有者がいましたが、ここでGCを使用すると、強い参照と弱い参照の違いについて適切に考えるのではなく、開発者があらゆる場所で所有権を共有するようになりました。参照とファントム参照。

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

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

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

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

これは私のドメインでの課題であり、deletenewを忘れないでください(おかしなテスト、実践、およびツールでアマチュア時間を話しているのでない限り)。そして、GCを使用しているかどうかに注意を払う必要があります。

マルチスレッド

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

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

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

C++

最後に:

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

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

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

1
Dragon Energy