エデンスペース
だから私の質問はこれのどれでも本当に真実でありえ、もしそうなら、なぜJavaのヒープ割り当てがそんなに速くなるのかということです。
Java GCは非常に興味深いので、Java GCがどのように機能するかについて少し勉強しています。私は常にCとC ++のメモリ割り当て戦略のコレクションを拡張しようとしています(Cに似たものを実装しようとすることに興味があります)、それは大量のオブジェクトをバースト形式で非常に高速に割り当てる方法です実用的な観点ですが、主にマルチスレッドが原因です。
Java GCの割り当てが機能する方法は、非常に安価な割り当て戦略を使用して、最初にオブジェクトを「Eden」スペースに割り当てることです。私が言えることから、それはシーケンシャルプールアロケーターを使用しています。
これは、アルゴリズムやmalloc
Cでの汎用目的operator new
、C ++ でのスローよりも、強制的にページフォールトを減らすという点で非常に高速です。
ただし、シーケンシャルアロケーターには大きな欠点があります。可変サイズのチャンクを割り当てることはできますが、個々のチャンクを解放することはできません。アラインメントのためのパディングを使用して、まっすぐに連続的に割り当てられ、一度に割り当てたすべてのメモリのみをパージできます。通常、CおよびC ++では、要素の挿入のみを行い、要素の削除は不要なデータ構造の構築に役立ちます。たとえば、プログラムの起動時に1回だけ構築する必要があり、繰り返し検索されるか、新しいキーのみが追加される検索ツリー(キーは削除されません)。
また、要素を削除できるデータ構造にも使用できますが、個別に割り当てを解除できないため、これらの要素は実際にはメモリから解放されません。シーケンシャルアロケーターを使用するこのような構造は、別のシーケンシャルアロケーターを使用してデータを新しい圧縮コピーにコピーする遅延パスがない限り、メモリを消費しますなんらかの理由で行うことはありません-データ構造の新しいコピーを順番に割り当てて、古い構造のすべてのメモリをダンプするだけです)。
コレクション
上記のデータ構造/シーケンシャルプールの例のように、Java GCがこの方法のみを割り当てた場合、多数の個々のチャンクのバースト割り当てが非常に高速であっても、大きな問題になります。ソフトウェアがシャットダウンされるまで何も解放できず、その時点ですべてのメモリプールを一度に解放(パージ)できます。
そのため、代わりに、1回のGCサイクルの後、「Eden」スペース内の既存のオブジェクト(連続的に割り当てられた)を通過し、まだ参照されているオブジェクトは、個々のチャンクを解放できるより汎用的なアロケーターを使用して割り当てられます。参照されなくなったものは、パージの過程で単純に割り当て解除されます。したがって、基本的には、「まだ参照されている場合はEdenスペースからオブジェクトをコピーし、パージします」。
通常、これは非常に高価になるため、元々すべてのメモリを割り当てていたスレッドが大幅に停止することを避けるために、別のバックグラウンドスレッドで行われます。
初期のGCサイクル後に個々のチャンクを解放できるこのより高価なスキームを使用して、メモリがEdenスペースからコピーされ、割り当てられると、オブジェクトはより永続的なメモリ領域に移動します。これらの個々のチャンクは、参照されなくなると、その後のGCサイクルで解放されます。
速度
つまり、大まかに言えば、Java GCがストレートヒープ割り当てでCまたはC ++を非常に上回る可能性がある理由は、メモリの割り当てを要求するスレッドで最も安価で完全に一般化されていない割り当て戦略を使用しているためです。次に、ストレートアップのようなより一般的なアロケーターを使用するときに通常行う必要のある、より高価な作業を節約しますmalloc
、別のスレッドのます。
概念的には、GCは実際には全体としてより多くの作業を行う必要がありますが、1つのスレッドで全額が前払いされないように、スレッド全体に分散しています。これにより、スレッドをメモリに割り当てて非常に安価に実行でき、個々のオブジェクトを実際に別のスレッドに解放できるように、適切な処理に必要な真の出費を延期できます。CまたはC ++では、をmalloc
呼び出すときにoperator new
、同じスレッド内で全額を前払いする必要があります。
これが主な違いであり、JavaがCまたはC ++よりも単純な呼び出しを使用するmalloc
かoperator new
、多数の小さなチャンクを個別に割り当てることで、CまたはC ++を非常に上回ります。もちろん、通常、GCサイクルが開始されると、アトミック操作とロックの可能性がいくつかありますが、おそらくかなり最適化されています。
基本的に、簡単な説明は、シングルスレッドでより重いコストを支払うこと(malloc
)とシングルスレッドでより安いコストを支払い、次に並行して実行できる別のスレッドでより重いコストを支払うこと(GC
)に要約されます。この方法の欠点は、アロケータが既存のオブジェクト参照を無効にせずにメモリをコピー/移動できるようにするために、オブジェクト参照からオブジェクトに取得するために2つのインダイレクションが必要であることを意味します「エデン」スペースから移動しました。
最後に大事なことを言い忘れましたが、C ++コードは通常、ヒープにオブジェクトのボートロードを個別に割り当てないため、比較は少し不公平です。まともなC ++コードは、連続ブロックまたはスタック上の多くの要素にメモリを割り当てる傾向があります。無料のストアで一度に1つの小さなオブジェクトのボートロードを割り当てる場合、コードはシテです。