JVMの自己治癒能力

29853 ワード

IT業界では、問題に直面した最初の反応は「再起動したことがありますか」であり、逆効果になる可能性があります.本稿では、このようなシーンについて説明します.
次に紹介するこのアプリケーションは、再起動する必要はありません.誇張せずに、実行を始めたばかりの頃は挫折するかもしれませんが、佳境に入ることができます.実際にその自己治癒能力を示すために、私たちはできるだけ簡単にこのシーンを再現しました.このインスピレーションは5年前にheinz Kabutzが発表した古い文章のおかげです.
package eu.plumbr.test;

public class HealMe {
  private static final int SIZE = (int) (Runtime.getRuntime().maxMemory() * 0.6);

  public static void main(String[] args) throws Exception {
    for (int i = 0; i < 1000; i++) {
      allocateMemory(i);
    }
  }

  private static void allocateMemory(int i) {
    try {
      {
        byte[] bytes = new byte[SIZE];
        System.out.println(bytes.length);
      }

      byte[] moreBytes = new byte[SIZE];
      System.out.println(moreBytes.length);

      System.out.println("I allocated memory successfully " + i);

    } catch (OutOfMemoryError e) {
      System.out.println("I failed to allocate memory " + i);
    }
  }
}

上記のコードは、2つのメモリを循環的に割り当てます.割り当てられるメモリは、スタック内の合計メモリの60%です.同じ方法でこのメモリ割り当てが絶えず行われるため、このコードはjavaを絶えず投げ出すと考えられるかもしれません.lang.OutOfMemoryError:Java heap space異常、allocateMemoryメソッドを正常に実行できません.
まず、ソースコードの静的解析を行い、この推測が適切かどうかを見てみましょう.
このプログラムを一見すると、JVMの制限を超えて割り当てられるメモリが正常に実行されません.
しかし、さらに詳しく分析すると、最初の割り当てはブロックの役割ドメイン内で完了していることがわかります.つまり、このブロックで定義された変数はブロック内にのみ表示されます.これは,これらにこのコードブロックが存在して実行が完了すると回収できることを意味する.このコードは最初は正常に実行できるはずですが、moreBytesの割り当てを試みたときに切られます.
コンパイルされたclassファイルをもう一度確認すると、というバイトコードが表示されます.
private static void allocateMemory(int);
    Code:
       0: getstatic     #3                  // Field SIZE:I
       3: newarray       byte
       5: astore_1      
       6: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
       9: aload_1       
      10: arraylength   
      11: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
      14: getstatic     #3                  // Field SIZE:I
      17: newarray       byte
      19: astore_1      
      20: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      23: aload_1       
      24: arraylength   
      25: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
---- cut for brevity ----

ここで、第1の配列は、位置3〜5で割り付けが完了し、シーケンス番号1のローカル変数に格納されていることがわかる.次に、位置17において、別の配列を割り当てる.ただし、最初の配列はローカル変数で参照されているため、2回目の割り当ては常にOOMの例外を放出して失敗します.バイトコード解釈器は、依然として強い参照が存在するため、GCが最初の配列を回収することを許さない.
静的コード解析から,上記のコードは,下位層の2つの制約により正常に実行できず,第1の場合に実行できることが分かった.この3つの分析の中でどれが正しいのでしょうか.実際に実行して結果を見てみましょう.結果は,これらの結論が正しいことを示した.まず、アプリケーションはメモリを割り当てることができません.しかし、時間が経過すると(私のMac OS XでJava 8を使用すると255回目の反復に現れるかもしれません)、メモリ割り当てが正常に実行され始めます.
 java -Xmx2g eu.plumbr.test.HealMe
1145359564
I failed to allocate memory 0
1145359564
I failed to allocate memory 1

 cut for brevity ...

I failed to allocate memory 254
1145359564
I failed to allocate memory 255
1145359564
1145359564
I allocated memory successfully 256
1145359564
1145359564
I allocated memory successfully 257
1145359564
1145359564
Self-healing code is a reality! Skynet is near...

何が起こったのかを明らかにするために、プログラムの実行中に何が変わったのか考えなければなりません.明らかにJust-In-Timeコンパイルが介入し始めた.覚えているなら、JITコンパイルはJVMの組み込みメカニズムであり、ホットスポットコードを最適化することができます.JITは実行するコードを監視し、ホットスポットが見つかった場合、バイトコードをローカルコードに変換し、メソッドのインラインや不要なコード消去などの追加の最適化を実行します.
次のコマンドラインパラメータを開いて、JITコンパイルがトリガーされたかどうかを確認します.
-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:+LogCompilation

これはログファイルを生成します.ここではhotspotです.pid38139.logファイル、38139はJavaプロセスのPIDです.このファイルには、次の行があります.
<task_queued compile_id='94' method='HealMe allocateMemory (I)V' bytes='83' count='256' iicount='256' level='3' stamp='112.305' comment='tiered' hot_count='256'/>

これは,256回allocateMemory()メソッド2を実行した後,C 1コンパイラがこのメソッドを3段階コンパイルすることを決定したことを示している.ここでは、階層コンパイルの各レベルと異なるしきい値について説明します.前の256回の反復では、このプログラムは解釈モードで実行され、ここでのバイトコード解釈器は単純なスタックマシンであり、ある変数の後続が使用されるかどうかを事前に予知することができず、ここでは変数bytesに対応している.しかし、JITはメソッド全体を一度に表示するので、bytes変数は後で使用されないと推定し、GCすることができます.だからごみの回収をトリガーするので、私たちのプログラムは奇跡的に治ります.私はただ、本稿の読者が生産環境でデバッグのような問題に遭遇しないことを望んでいます.しかし、誰かを狂わせたいなら、生産環境に似たようなコードを加えてみてください.
オリジナル記事転載出典:JVMの癒し能力
英語のテキストリンク