2012-06-08 29 views
11

非常に大きなファイルを読み込み、各行からテキストの一部を抽出しています。しかし、オペレーションの終わりには、私は仕事のためのメモリがほとんど残っていません。ガベージコレクタは、ファイルを読み込んだ後にメモリを解放できないようです。Javaはガベージコレクトメモリではありません

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

これを実証するためにSSCCEを作成しました。それは1MB(16ビットエンコーディングのためにJavaで2MB)のファイルを読み込み、各行から1文字を抽出します(〜4000行、約8KBにする必要があります)。テストの終わりには、まだ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); 
    } 
} 
+3

他に誰もやっていないことが非常に珍しいことをしない限り、「jvm bug」は最初の仮定ではありません。 –

+1

特にgcに関して。 –

+0

どのようにSystem.gc()がすべてのメモリを解放すると思いますか?あなたはまだalの文字列を使用しているので、解放することはできません。 –

答えて

21

部分文字列を作成する場合は、あなたのサブストリングが(この最適化は、文字列の多くの部分文字列を扱うになり、元の文字列のchar型の配列への参照を保持しますとても早い)。したがって、部分文字列をalのリストに入れておくと、ファイル全体がメモリに保持されます。これを避けるには、文字列を引数として持つコンストラクタを使用して新しい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; 

だから、基本的に、私はあなたが

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

文字列のソースコードを実行することをお勧めしたい(String)コンストラクタを明示的にその使用は "荷物" をトリミングすることであると述べていますアップデート:この問題は、OpenJDK 7、Update 6ではなくなりました。最新バージョンの人には問題はありません。

+0

こんにちは...面白いです。これは、部分文字列が行う奇妙な最適化です。しかし、それは何が起こっているのかを説明します。また、バグ報告があるようです:http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4513622 – tskuzzy

+0

これは、最初のバージョンのjava(1.02)とそれはスマートな最適化と見なされました。問題は、それがより複雑になることです。 –

+0

'O(1)'操作で部分文字列を減らすので、その背後にある推論を見ることができます。しかし、これは私にとってメモリリークのように思える。 – tskuzzy

-1

System.gc()は、JVMがガベージコレクションを保証するものではありません.JVMが試してガベージコレクトできる唯一のアドバイスです。既に使用可能なメモリが多いため、JVMはアドバイスを無視して、実行する必要があると感じるまで実行し続けます。それについて語っ

読むより文書でhttp://docs.oracle.com/javase/6/docs/api/java/lang/System.html#gc()

もう一つの問題は、あなたがこれ以上を必要としないの参照を保持しないようにしてくださいWhen does System.gc() do anything

6

でご利用いただけます。

alinにはまだ参照があります。

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

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

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

部分文字列をコピーするよりエレガントな方法があるかどうかわかりません。たぶん s.getChars()はあなたにとっても便利です。

Java 8の場合、部分文字列になります。コンストラクタがArrays.copyOfRangeを呼び出すことを確認できます。

+0

私は 'al'の文字列を使う必要があります。私はファイルの残りの部分をガベージコレクションしたいだけです。 – tskuzzy

+0

少なくとも私のJDKでは、長さが異なるときに 'new String'が文字列をトリミングします。 –

関連する問題