ホームページ  >  記事  >  Java  >  Java ガベージ コレクションのオーバーヘッドを削減する方法

Java ガベージ コレクションのオーバーヘッドを削減する方法

PHPz
PHPz転載
2023-05-04 14:31:06649ブラウズ

ヒント #1: コレクションの容量を予測する

カスタムおよび拡張実装 (Trove や Google の Guava など) を含むすべての標準 Java コレクションは、内部で配列 (ネイティブ データ型またはオブジェクトベースの型のいずれか) を使用します。配列が一度割り当てられると、そのサイズは不変であるため、コレクションに要素を追加すると、ほとんどの場合、古い配列 (コレクションの基礎となる実装)。

コレクションの初期化サイズが指定されていない場合でも、ほとんどのコレクション実装は、配列の再割り当て処理を最適化し、そのオーバーヘッドを最小限に抑えようとします。ただし、コレクションを構築するときにサイズを指定すると、最良の結果が得られます。

簡単な例として次のコードを分析してみましょう:

public static List reverse(List & lt; ? extends T & gt; list) {

リストの結果 = new ArrayList();

for (int i = list.size() - 1; i & gt; = 0; i--) {

result.add(list.get(i));

}

結果を返す;

}

このメソッドは、新しい配列を割り当て、別のリストの項目を逆順でのみ埋め込みます。

この処理方法ではパフォーマンスに大きなコストがかかる可能性があり、最適化ポイントは要素を新しいリストに追加するコード行です。各要素が追加されるたびに、リストの基になる配列に新しい要素を収容するのに十分なスペースがあることを確認する必要があります。空きスロットがある場合、新しい要素は次の空きスロットに単純に保存されます。そうでない場合は、新しい基になる配列が割り当てられ、古い配列の内容が新しい配列にコピーされ、新しい要素が追加されます。これにより、配列が複数回割り当てられることになり、残った古い配列は最終的に GC によって回収されます。

コレクションを構築するときに、基礎となる配列に格納する要素の数を知らせることで、このような冗長な割り当てを回避できます。 public static List reverse(List & lt; ? extends T & gt; list) {

リストの結果 = new ArrayList(list.size());

for (int i = list.size() - 1; i & gt; = 0; i--) {

result.add(list.get(i));

}

結果を返す;

}

上記のコードは、ArrayList のコンストラクターを通じて list.size() 要素を格納するのに十分な大きさの領域を指定し、初期化中に割り当てを完了します。つまり、List は反復プロセス中に再度メモリを割り当てる必要がありません。

Guava のコレクション クラスはさらに一歩進んで、コレクションの初期化時に予期される要素の数を明示的に指定したり、予測値を指定したりできます。

1

2リストの結果 = Lists.newArrayListWithCapacity(list.size());

リストの結果 = Lists.newArrayListWithExpectedSize(list.size());

上記のコードでは、前者はコレクションに格納される要素の数がすでに正確にわかっている場合に使用され、後者は不正確な推定を考慮した方法で割り当てられます。

ヒント #2: データ ストリームを直接処理する

## ファイルからのデータの読み取りやネットワークからのデータのダウンロードなど、データ ストリームを扱う場合、次のコードが非常に一般的です。 1byte[] fileData = readFileToByteArray(new File("myfile.txt"));

結果として得られるバイト配列は、いくつかの一般的なオプションを使用して、XML ドキュメント、JSON オブジェクト、またはプロトコル バッファリングされたメッセージとして解析できます。

JVM が実際のファイルを処理するためのバッファを割り当てられない場合、OutOfMemoryErrors が発生するため、大きなファイルや予測不可能なサイズのファイルを扱う場合、上記のアプローチは賢明ではありません。

データのサイズが管理可能な場合でも、上記のパターンを使用すると、ファイル データを保存するためにヒープ内に非常に大きな領域が割り当てられるため、ガベージ コレクションに関しては依然として大きなオーバーヘッドが発生します。

これを処理するより良い方法は、ファイル全体を一度にバイト配列に読み取るのではなく、適切な InputStream (この例では FileInputStream など) を使用してパーサーに直接渡すことです。すべての主流のオープン ソース ライブラリは、処理のために入力ストリームを直接受け入れるための対応する API を提供します。 FileInputStream fis = 新しい FileInputStream(ファイル名);

MyProtoBufMessage msg = MyProtoBufMessage.parseFrom(fis);

ヒント #3: 不変オブジェクトを使用する

# 不変性には非常に多くの利点があります。詳細に入る必要すらありません。ただし、ガベージ コレクションに影響を与える注目すべき利点が 1 つあります。

不変オブジェクトのプロパティは、オブジェクトの作成後に変更できません (この例では参照データ型のプロパティを使用しています)。例:

パブリック クラス ObjectPair {

プライベートの最終オブジェクトを最初に;

プライベート最終オブジェクト 2 番目;

public ObjectPair(最初のオブジェクト、2 番目のオブジェクト) {

this.first = 最初;

this.秒 = 秒;

}

public Object getFirst() {

最初に戻る;

}

public Object getSecond() {

2 番目を返す;

}

}

上記のクラスをインスタンス化すると、不変オブジェクトが生成されます。そのすべてのプロパティは Final で変更され、構築が完了した後は変更できません。

不変性とは、不変コンテナによって参照されるすべてのオブジェクトが、コンテナが構築される前に作成されることを意味します。 GC に関する限り、コンテナーは、少なくともコンテナーが保持する最も新しい参照と同じくらい新しいものです。これは、若い世代でガベージ コレクションを実行する場合、GC は古い世代にある不変オブジェクトをスキップし、これらの不変オブジェクトが世代内のどのオブジェクトからも参照されていないと判断されるまで不変オブジェクトのコレクションを完了しないことを意味します。古い世代、リサイクル。

スキャンするオブジェクトが少ないということは、メモリ ページのスキャンが少ないことを意味し、メモリ ページのスキャンが少ないということは、GC ライフ サイクルが短いことを意味します。これは、GC の一時停止が短くなり、全体的なスループットが向上することも意味します。

ヒント #4: 文字列の連結には注意してください

文字列は、おそらくすべての JVM ベースのアプリケーションで最も一般的に使用される非ネイティブ データ構造です。ただし、暗黙的なオーバーヘッドと使いやすさのため、大量のメモリを消費する原因となることが非常に簡単です。

問題は明らかに文字列リテラルではなく、実行時の割り当てられたメモリの初期化にあります。文字列を動的に構築する例を簡単に見てみましょう:

public static String toString(T[] array) {

文字列結果 = "[";

for (int i = 0; i & lt; array.length; i ) {

result = (array[i] == array ? "this" : array[i]);

if (i & lt; array.length - 1) {

結果 = ", ";

}

}

結果 = "]";

結果を返す;

}

これは、文字の配列を取得して文字列を返す、一見良いメソッドのように見えます。しかし、これはオブジェクトのメモリ割り当てにとって悲惨です。

この構文シュガーの背後を見るのは難しいですが、舞台裏の実際の状況は次のとおりです:

public static String toString(T[] array) {

文字列結果 = "[";

for (int i = 0; i & lt; array.length; i ) {

StringBuilder sb1 = 新しい StringBuilder(結果);

sb1.append(array[i] == array ? "this" : array[i]);

結果 = sb1.toString();

if (i & lt; array.length - 1) {

StringBuilder sb2 = 新しい StringBuilder(結果);

sb2.append(", ");

結果 = sb2.toString();

}

}

StringBuilder sb3 = 新しい StringBuilder(結果);

sb3.append("]");

結果 = sb3.toString();

結果を返す;

}

文字列は不変です。つまり、連結が発生するたびに文字列自体は変更されませんが、新しい文字列が順番に割り当てられます。さらに、コンパイラは標準の StringBuilder クラスを使用してこれらの連結操作を実行します。これは、最終結果の構築に役立つように各反復で一時文字列と一時 StringBuilder オブジェクトが暗黙的に割り当てられるため、問題となります。

最善の方法は、上記の状況を回避し、ネイティブの連結演算子 (" ") の代わりに StringBuilder を使用して直接追加することです。以下は例です:

public static String toString(T[] array) {

StringBuilder sb = new StringBuilder("[");

for (int i = 0; i & lt; array.length; i ) {

sb.append(array[i] == array ? "this" : array[i]);

if (i & lt; array.length - 1) {

sb.append(", ");

}

}

sb.append("]");

戻り sb.toString();

}

ここでは、唯一の StringBuilder をメソッドの先頭に割り当てます。この時点で、すべての文字列とリスト要素が 1 つの StringBuilder に追加されました。最後に、toString() メソッドを使用して文字列に変換し、一度に返します。

ヒント #5: 特定のネイティブ型のコレクションを使用する

Java の標準コレクション ライブラリはシンプルでジェネリックをサポートしているため、コレクション使用時に型を半静的にバインドできます。たとえば、文字列のみを格納する Set や Map を格納するマップを作成する場合、このアプローチは最適です。

本当の問題は、リストを使用して int 型を保存する場合、またはマップを使用して double 型を値として保存する場合に発生します。ジェネリックはネイティブ データ型をサポートしていないため、別のオプションは代わりにラッパー型を使用することです。ここでは List を使用します。

Integer は完全なオブジェクトであるため、この処理方法は非常に無駄です。オブジェクトのヘッダーは 12 バイトを占め、int プロパティはその内部に保持されます。各 Integer オブジェクトは合計 16 バイトを占めます。これは、同じ数の項目を格納する int 型のリストの 4 倍のスペースを消費します。これよりも深刻な問題は、Integer は実際のオブジェクト インスタンスであるため、ガベージ コレクション段階でリサイクルのためにガベージ コレクターによって考慮される必要があるという事実です。

これに対処するために、Takipi の素晴らしい Trove コレクション ライブラリを使用します。 Trove は、メモリ効率の高いネイティブ型の特殊なコレクションを優先して、いくつかの一般的な特異性を放棄します。たとえば、非常にパフォーマンスを消費する Map を使用します。Trove には、TIntDoubleMap

の形式の別の特別なオプションがあります。

TIntDoubleMap マップ = 新しい TIntDoubleHashMap();

map.put(5, 7.0);

map.put(-1, 9.999);

...

Trove の基になる実装はネイティブ型の配列を使用するため、コレクションを操作するとき、要素のボックス化 (int->Integer) またはアンボックス化 (Integer->int) は発生せず、オブジェクトは格納されません。ネイティブ データ型ストレージ。

以上がJava ガベージ コレクションのオーバーヘッドを削減する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。