GC (garbage collection)

  1. Generational GC
  2. Parallel GC
  3. Mostly-Concurrent GC
  4. ヒープを無駄に使わないプログラミングの知恵
java で GC を採用した利点は、メモリへの不正参照をさせない、 メモリリークを防止できる、プログラムの生産性が向上するなどがあります。

GC の基本的な機能は、プログラムで作ったオブジェクトの、 その時点での使用、未使用を決定し、すでに未使用になった領域を回収します。

使用、未使用の決定は、heap にあるオブジェクトで参照がないものにマークをつけ、 実行中の全スレッドを一旦停止して (stop-the-world)、 使用中のオブジェクトを別の heap エリアに移動し、 それらを参照するアドレスをすべて付け替えます。 この方法は、始めから変わっていません。 この処理を効率化するために、どのように改良したかについての説明します。

GC スレッドが動くときは、その他の全スレッドは動けません(stop-the-world)。 それは、GC がオブジェクトを移動させるためで、 その時同時にそれらを参照、更新できないからです。 統計によると、生成されたオブジェクトの 90% 以上がすぐに使われなくなります。 従ってオブジェクトは世代管理 (generation) するのが有効で、 Generational GC と呼ばれています。

  1. Generational GC
  2. JVM heap layout

    eden space from space to space young generation
    (2MB) (64KMB) (64KMB)
    tenured space old generation
    (5MB min,44MB max)
    permanent space permanent generation
    (4MB)

    (*: 各サイズはデフォルト値)

    ヒープには young generation、old generation と permanent generation があります。 young generation には eden-space と 2 つの survivor space (s0:from-space,s1:to-space) があります。 オブジェクトはまず eden-space または from-space に確保されます。 通常 to-space は空にしておきます。 この young generation 用の領域は、全体の heap サイズからすると小さい部分です。 ここがいっぱいになると、minor GC が起こります。 従って minor GC は頻繁に行われます。

    まず eden と from-space のほとんどのオブジェクトはすでに未使用となっていますので、 それらは回収されますが、 まだ使用中のオブジェクトは to-space に移動されます。 しかし to-space がいっぱいになる、または大きなサイズのオブジェクトは old generation にも移動されます。 その後 to と from の名前を交換します。 minor GC が完了すると eden-space は空となり、 そこは新たに作られるオブジェクトのための領域となります。

    young generation に長くいるオブジェクトは old generation に移されます。 他方 old generation に残るオブジェクトは、しだいに詰まってきます。 young generation の GC は minor GC、old generation の GC は major GC と呼ばれます。 old generation がいっぱいになると、major GC がおこります。 これは、頻度はすくないですが、時間がかります。

    あと1つ permanent generation があり、 これは JVM のクラスやメソッドオブジェクトが使う特別なデータ領域で、 ずっと存在しつづけ、GC の対象にはなりません。

    未使用領域は free list に登録されます。 この場合 old generation が断片化 (fragmentation) する問題があります。 そのために compact します。

    いくつかの例外があります。 young generation で領域が確保できない場合や、大きなオブジェクトは、 直接 old generation に確保されます。

  3. Parallel GC
  4. 通常 GC は単一スレッドで動きますが、 GC 部分を等分して、複数スレッドで同時に各 GC を平行処理しようとするのが、 Parallel GC です。 CPU 数の多いマシン上では高効率です。

  5. Mostly-Concurrent GC
  6. これは major GC で使われる手法で、一般には CMS (Concurrent Mark-Sweep) と呼ばれます。

    CMS は GC をマークフェーズとスイープフェーズに分け、 これらを通常のアプリケーションスレッドと同時に行います。

    ただし、マークをするときにマーク漏れを防ぐため、 すべてのスレッドをストップさせてリマーク処理を行う必要があります。 しかし、アプリケーションスレッドが止まるのは、 このリマーク処理とスイープが終了した後の初期化の間だけなので、 停止時間は短くなり、高速化しています。

  7. ヒープを無駄に使わないプログラミングの知恵
  8. しかし何はともあれヒープを無駄に使わないプログラミングの知恵は必要でしょう。

    1. 使い終わったオブジェクトは明確に不要宣言する

         Connection conn = new Connection();
                 :
         conn = null;
      

    2. 不要なオブジェクトは作らない

         Public void paint(Graphics g) {
          g.fillRect(0,0,getSize().width,
                         getSize().height);
         }
      
       ==>
         
         Public void paint(Graphics g) {
          Dimension size = getSize();
          g.fillRect(0,0,size.width,
                         size.height);
         }
      

    3. String literal を使うべき。 なぜなら、余分な領域を必要としないからです。

         static final String s1 = "Hello";     
         static final String s2 = "Hello";
      
         public static void main(String[] argc) {
          String  s3 = new String("World");
          String  s4 = new String("World");
          boolean b1 = (s1 == s2);      // True
          boolean b2 = (s3 == s4);      // False
          boolean b3 = s3.equals(s4);   // True
          System.out.println("b1 = "+b1+"  b2 = "+b2+"  b3 = "+b3);
         }
      

    4. 可変長の文字列を扱うときは String でなく StringBuffer をつかうべき。 StringBuffer オブジェクトは、変更可能なので、 処理の中間結果を保持するために新たにオブジェクトを 作る必要がなく、 効率のよいコードを生成できます。

          String   s="",s_i;
      
          for (i=0; i<MAX_SIZE; i++)  {
            ii  = new Integer(i);
            s_i = ii.toString();
            s  += s_i;
          }
       ==>
          StringBuffer   sb = new StringBuffer();
      
          for (i=0; i<MAX_SIZE; i++)  {
            ii  = new Integer(i);
            sb  = sb.append(ii.toString());
          }
      

    5. System.gc() をプログラムから呼ぶことはしないでください。 JVM が GC をいつすべきか自動的に判断します。

    6. よく使うオブジェクトはプールする。たとえばデータベースへの接続など。

    7. GC の最適化 またGC を最適化するためのパラメータが 200 近く用意されているそうです。 これらを調整して最適化するためには profile を取得してください。

    8. JVM の起動に関するオプション JVM を server か client 用に最適化します。

        > java -server          JVM for server
        > java -client          JVM for client
      

参考資料

http://developer.java.sun.com/developer/technicalArticles/Programming/GCPortal
http://java.sun.com/products/hotspot/