12

私は非常に大きなファイルを読んでいて、各行からテキストのいくつかの小さな部分を抽出しています。しかし、操作の終わりに、私は作業するためのメモリがほとんど残っていません。ガベージコレクターは、ファイルを読み込んだ後、メモリを解放できないようです。

私の質問は:このメモリを解放する方法はありますか?それとも、これはJVMのバグですか?

これを実証するためにSSCCEを作成しました。1 mb(16ビットエンコーディングのためJavaでは2 mb)ファイルを読み込み、各行から1文字を抽出します(約4000行なので、約8 kbにする必要があります)。テストの終了時に、2 mb全体がまだ使用されています!

初期メモリ使用量:

Allocated: 93847.55 kb
Free: 93357.23 kb

ファイルを読み込んだ直後(手動のガベージコレクションの前):

Allocated: 93847.55 kb
Free: 77613.45 kb (~16mb used)

プログラムはファイルの読み取りに多くのリソースを使用しているため、これは予想されることです。

ただし、ガベージコレクションを実行しますが、すべてのメモリが解放されるわけではありません。

Allocated: 93847.55 kb
Free: 91214.78 kb (~2 mb used! That's the entire file!)

ガベージコレクターを手動で呼び出しても、保証が得られないことはわかっています(場合によっては怠惰です)。ただし、これは、ファイルが使用可能なメモリのほとんどすべてを消費し、プログラムの残りの部分が必要であるにもかかわらずメモリを使い果たしてしまう、私の大きなアプリケーションで発生していました。この例は、ファイルから読み取られた余分なデータが解放されていないという私の疑いを裏付けています。

テストを生成するためのSSCCEは次のとおりです。

import java.io.*;
import java.util.*;

public class Test {
    public static void main(String[] args) throws Throwable {
        Runtime rt = Runtime.getRuntime();

        double alloc = rt.totalMemory()/1000.0;
        double free = rt.freeMemory()/1000.0;

        System.out.printf("Allocated: %.2f kb\nFree: %.2f kb\n\n",alloc,free);

        Scanner in = new Scanner(new File("my_file.txt"));
        ArrayList<String> al = new ArrayList<String>();

        while(in.hasNextLine()) {
            String s = in.nextLine();
            al.add(s.substring(0,1)); // extracts first 1 character
        }

        alloc = rt.totalMemory()/1000.0;
        free = rt.freeMemory()/1000.0;
        System.out.printf("Allocated: %.2f kb\nFree: %.2f kb\n\n",alloc,free);

        in.close();
        System.gc();

        alloc = rt.totalMemory()/1000.0;
        free = rt.freeMemory()/1000.0;
        System.out.printf("Allocated: %.2f kb\nFree: %.2f kb\n\n",alloc,free);
    }
}
4

3 に答える 3

22

サブストリングを作成するとき、サブストリングは元のストリングのchar配列への参照を保持します(この最適化により、ストリングの多くのサブストリングの処理が非常に高速になります)。したがって、サブ文字列をalリストに保持すると、ファイル全体がメモリに保持されます。これを回避するには、引数として文字列を受け取るコンストラクターを使用して新しい文字列を作成します。

だから基本的に私はあなたがすることをお勧めします

    while(in.hasNextLine()) {
        String s = in.nextLine();
        al.add(new String(s.substring(0,1))); // extracts first 1 character
    }

String(String)コンストラクターのソースコードは、その使用法が「手荷物」をトリミングすることであることを明示的に示しています。

  164       public String(String original) {
  165           int size = original.count;
  166           char[] originalValue = original.value;
  167           char[] v;
  168           if (originalValue.length > size) {
  169               // The array representing the String is bigger than the new
  170               // String itself.  Perhaps this constructor is being called
  171               // in order to trim the baggage, so make a copy of the array.
  172               int off = original.offset;
  173               v = Arrays.copyOfRange(originalValue, off, off+size);
  174           } else {
  175               // The array representing the String is the same
  176               // size as the String, so no point in making a copy.
  177               v = originalValue;
  178           }
  179           this.offset = 0;
  180           this.count = size;
  181           this.value = v;

アップデート:この問題はOpenJDK 7、アップデート6で解消されました。より新しいバージョンの人は問題を抱えていません。

于 2012-06-08T15:35:47.887 に答える
6

不要になった参照を保持しないようにしてください。

とへの参照はまだありalますin

al = null; in = null;ガベージコレクタを呼び出す前に追加してみてください。

また、どのようsubstringに実装されているかを理解する必要があります。substring元の文字列を保持し、同じchar[]配列に対して異なるオフセットと長さを使用します。

al.add(new String(s.substring(0,1)));

部分文字列をコピーするより洗練された方法があるかどうかはわかりません。たぶんs.getChars()あなたにとってももっと便利でしょう。

Java 8以降、サブストリング文字をコピーするようになりました。コンストラクターがを呼び出すことを確認できますArrays.copyOfRange

于 2012-06-08T15:38:20.123 に答える
-1

System.gc()は、JVMがガベージコレクションを実行することを保証するものではありません。これは、JVMがガベージコレクションを試行できることをJVMに通知するだけです。すでに使用可能なメモリがたくさんあるため、JVMはアドバイスを無視し、必要性を感じるまで実行を続ける場合があります。

詳細については、ドキュメントhttp://docs.oracle.com/javase/6/docs/api/java/lang/System.html#gc()を参照してください。

それについて話す別の質問は、 System.gc()が何かをするときで利用できます。

于 2012-06-08T15:38:19.233 に答える