2009-07-08 1 views
3

は、私は、コードJavaメモリーパズル

package memoryleak; 

public class MemoryLeak { 

    public static int size; 

    static { 
     size = (int) (Runtime.getRuntime().maxMemory()*0.6); 
    } 

    public static void main(String[] args) throws InterruptedException { 
     { 
      byte[] data1 = new byte[size]; 
     } 

     byte[] data2 = new byte[size]; 
    } 
} 

このコードはOutOfMemoryErrorの発生を以下していると仮定します。このコードを1つの変数割り当て(最初の配列で使用されているスタックフレームを書き換え、配列をガーベジコレクションのために使用可能にする)で動作させることができます。このパズルはhereを説明しました。

{ 
    byte[] data1 = new byte[size]; 
} 
int i = 0; 
byte[] data2 = new byte[size]; 

問題は次のコードがまだ機能しないのはなぜですか?

Object o = new Object(); 
synchronized (o) { 
    byte[] data1 = new byte[size]; 
} 
int i = 0; 
byte[] data2 = new byte[size]; 

そして、次の作品は:

Object o = new Object(); 
synchronized (o) { 
    byte[] data1 = new byte[size]; 
} 
int i = 0; 
synchronized (o) { 
    byte[] data2 = new byte[size]; 
} 
+1

yuck ...この動作は完全にガベージコレクタの実装に依存しませんか?これは、この振る舞いが確定的ではないようです(つまり、JLSでは指定されていません)。私が間違っている? – Tom

+2

JLSでは、何もしないでGCを実装することができます(どのようにGCの契約を指定しますか?)。 –

+1

記事にリンクされている記事を見ると、GCに依存するようには見えませんが、生成されたバイトコード(スタック上の配列リファレンスを長時間すぎないようにします)にあります。一方、この記事では、BEAやIBMのVMやSunの実験的な新しいガベージコレクタでは失敗しないとも述べています。混乱... – Thilo

答えて

6

私の賭けは​​がフレームに要素を追加することで行うことができませんでした。​​は、ローカル/フィールドが変更された場合でも、ロックされた同じオブジェクトのロックを解除する必要があります。

​​コードは次のようになります:

Object $sync = o; 
$sync.lock(); 
try { 
    byte[] data1 = new byte[size]; 
} finally { 
    $sync.unlock(); 
} 

だから、コードの最後のサンプルを取る:

Object o = new Object();   // Slot 0. 
synchronized (o) {     // Slot 1. 
    byte[] data1 = new byte[size]; // Slot 2. 
}         
int i = 0;       // Slot 1. 
synchronized (o) {     // Slot 2. (clobbers data1, was slot 1) 
    byte[] data2 = new byte[size]; // Slot 3. 
} 
+0

そうだね。 public static void main(String [] args)throws InterruptedException { synchronized(新規オブジェクト()){ byte [] data1 = new byte [size]; } int i = 0; int k = 0; バイト[]データ2 =新しいバイト[サイズ]; } 非常にうまく動作します。このコードを逆アセンブルすると、 "k"変数が "data1"のスタックフレームを上書きすることがわかります。 –

+0

スピーチレス。素晴らしいもの。 –

-1

あなたのインスタンス化の前に収集するために、GCに頼っていますか?

あなたはiによって上書きされますスロットを上に移動していないためにdata1を引き起こし、

Object o = new Object(); 
byte[] data1 = new byte[size]; 
GC.Collect() 
byte[] data2 = new byte[size]; 
+1

GCは、そのドキュメントに記載されているように、常にOutOfMemoryErrorを投げる前に収集します。http://java.sun.com/javase/6/docs/api/java/lang/OutOfMemoryError.html信頼できる行動。コードは範囲外ではないため、データ1を安全に再利用できることを明確に示しているため、信頼性の高いデータ1を収集する必要はありません。 – Mnementh

+0

彼はする必要はないはずだ。 JVMはOutOfMemoryErrorを投げる前にメモリを解放することを本当に頑張らなければならないので、ガベージコレクタは自動的に呼び出され、フルスイープモードでも呼び出されます。 – Thilo

+0

* GCは常にOutOfMemoryErrorを投げる前に収集しています(java.sun.com/javase/6/...)。これは少なくとも信頼性の高い動作です* 興味深い:私はこれを知らなかった。しかし、ガベージコレクタは、1つのサイクルですべての未使用オブジェクトを収集するとは保証していないため、依然として確定的ではありません。 http://java.sun.com/javase/technologies/hotspot/gc/gc_tuning_6.html#par_gc.oomで説明しています。 – cygil

0

パズルは面白いですが、考えたくはありません実用的なプログラマのためのガベージコレクションのもっと難解な側面については(もっと重要なことに依存して)、もはや問題が解決しなくなったらすぐにdata1 = nullを設定しますか?もしそうなら、私はむしろ奇妙な同期ブロックとダミー変数マジックをしたいと思います。

もちろん、配列が範囲外になるとすぐにメモリが解放されないということは悲しいことです。これは人々が望んでいたものですthis thread

これはJVMで修正する必要があります。

+0

もちろん、実践的なプログラマーは無知のために生産上の問題を引き起こすまでは問題ありません。 –

+0

このような詳細で絡み合うことを拒否することが無知とみなされるかどうかは不明です。これはまれなエッジケースのように思えますが、JVMの専門家以外は誰も知っていることは期待できません。 – Thilo

0

この動作はすべて実装によって異なります。ガベージコレクタは、プログラムの同期動作とは関係のない独自の非同期スレッドで動作します。 data1で参照される配列がいつガベージコレクションされるのか分からないだけです。範囲外になった後の「合理的な」時間内に起こることを望みます。

プログラムでメモリ不足が心配な場合は、System.gc()を使用して明示的にガベージコレクションサイクルをトリガできます。しかし、これでも、data2を割り当てるときに十分なメモリが使用可能になるとは限りません。 System.gc()を呼び出すことは、ガベージコレクションのサイクルを今すぐ実行したいというヒントです。

Javaでは、メモリの割り当てと割り当て解除が非決定的です。ガベージコレクタは実行時に実行され、プログラムレベルで実行することはできません。あなたが投稿したコードスニペットには、関連性のある違いはありません。なぜなら、gcの動作は非決定的であり、トリガされる正確なタイミングは実装およびシステムに依存するからです。場合によっては、これがアプリケーションの問題です。たとえば、OSの場合やメモリに制約のある組み込みデバイスで実行している場合、C++やメモリ管理が決定的な言語でコーディングする必要があります。しかし、ほとんどの人にとってガベージコレクタは合理的に妥当な方法で動作し、ほとんどの目的には十分だと信じていますが、問題を引き起こすような人為的なコードを作成することは可能です。

更新:恥ずかしがり屋。いくつかのコメント者が私に思い出させるように、jvmがOutOfMemoryエラーをスローする前に、ガベージコレクションサイクルが明示的に起動されます。しかし、この動作はまだ決定的ではありません。this linkでは、jvmは1つのガーベジコレクションサイクルですべてのデッドオブジェクトが検出されるとは保証しません。

+0

しかし、OutOfMemoryErrorを投げる前に、JVMが完全な(バックグラウンドではない)ガベージコレクションを強制しないのですか? – Thilo