torutkのブログ

ソフトウェア・エンジニアのブログ

64bit版Javaの実力

Windows Vista 64bit版上で、JDK 1.6 x86/x64版をインストールして、64bit版の能力を探ってみました。

実験環境の構成

CPU AMD Athlon 64 x2 4200+
メモリ 2GB
OS Windows Vista Ultimate 64bit
JVM Sun JDK 1.6 U2(1.6.0_02)

64bit長のデータアクセス

2つのスレッドを並列実行させ(デュアルコアPC)、スレッド間で共有する64bitプリミティブ型(long)を排他制御なしに1つのスレッドがwriteしもう1つのスレッドがreadし、意図しない値(32bitで分断)が観測されるか否かを実験しました。writeスレッドは、共有変数に次の2つの値を交互に書き込み続けます。

  • 0xAAAAAAAAAAAAAAAA
  • 0x5555555555555555

readスレッドは、共有変数から読み込んだ回数と、読み込んだ値が上記2つに該当しない回数を計測します。

計測結果
JDK 6(x86) JDK 6(x64)
21万回/10億回中 0回/31億回中

JDKの64bit版実装では、longのアクセスが32bitで分断されることはないようです。

以下が実験コードです。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class VolatileReaderWriter {

    private static long sharedValue;
    private final static long WRITE_VALUE_1 = 0xaaaaaaaaaaaaaaaaL;
    private final static long WRITE_VALUE_2 = 0x5555555555555555L;

    static class Writer implements Runnable {
        public void run() {
            boolean isOne = true;
            while (true) {
                sharedValue = isOne ? WRITE_VALUE_1 : WRITE_VALUE_2;
                isOne = !isOne;
            }
        }
    }

    static class Reader implements Runnable {
        private long numTotal;
        private long numFailure;

        public void run() {
            while (true) {
                numTotal++;
                long readValue = sharedValue;
                if (readValue != WRITE_VALUE_1 && readValue != WRITE_VALUE_2) {
                    System.out.printf(
                        "不正値を読み込み:%x (%d/%d)%n",
                        readValue, ++numFailure, numTotal
                    );
                }

            }
        }

        synchronized long getNumTotal() {
            return numTotal;
        }

        synchronized long getNumFailure() {
            return numFailure;
        }
    }

    static class Hook extends Thread {
        private Reader reader;

        public Hook(Reader aReader) {
            reader = aReader;
        }

        public void run() {
            System.out.println("Now shuttin down VolatileReaderWriter Program");
            System.out.println("Total number = " + reader.getNumTotal());
            System.out.println("Failure count = " + reader.getNumFailure());
        }
    }

    public static final void main(final String[] args) {
        ExecutorService service = Executors.newCachedThreadPool();
        Writer writer = new Writer();
        service.execute(writer);
        Reader reader = new Reader();
        service.execute(reader);
        Runtime.getRuntime().addShutdownHook(new Hook(reader));
    }
}

スレッド生成最大数

スレッドをどんどん生成し、スレッド生成可能個数の上限を実験しました。
スレッドが終了しないように、CyclicBarrierでawaitして待たせるコードにしています。

計測結果
JDK 6(x86) JDK 6(x64)
3071個 2万個

JDK 32bit版では、OutOfMemoryError: unable to create new native thread のエラーが発生します。

JDK 64bit版では、2万個を越えたあたりでPCのメモリ搭載量を越えるようになり、スワップのためシステム全体が非常に重くなりますが、エラーは発生しません。
したがって、物理メモリを増やせば、さらに上限個数が増えるものと考えます。

以下に実験コードを示します。

import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.BrokenBarrierException;

public class UnboundThreadCreator3 {

    public static final void main(final String[] args) {
        if (args.length < 1) {
            System.err.println("Usage: java UnboundThreadCreator3 <NumThreads>");
            System.exit(-1);
        }
        int numThreads = Integer.parseInt(args[0]);
        System.out.println("Number of Thread = " + numThreads);

        final CyclicBarrier barrier = new CyclicBarrier(numThreads);

        for (int i=0; i<numThreads; i++) {
            Thread thread = new Thread(new Runnable() {
                    public void run() {
                        int count = 10;
                        while (count > 0) {
                            try {
                                count--;
                                Thread.sleep(1000);
                            } catch (InterruptedException e) {
                                break;
                            }
                        }
                        try {
                            barrier.await();
                        } catch (InterruptedException e) {
                            System.err.println("Interrupted when awaiting");
                        } catch (BrokenBarrierException e) {
                            System.err.println("Broken barrier when awaiting");
                        }
                    }
                }
            );
            thread.start();
        }
    }
}

log4jの行方?

ちっちゃなプログラムをいくつも作る場合に、ロギングのためだけにlog4jを別途入れるというのも手間だし、ましてcommonsなんてかぶせてさらにJARを増やすのはやりたくない。ということで、普段はJava標準のロギングAPIを使っています。ですから、あまりlog4jの状況は追いかけていませんが、なにやらlog4j開発の中核人物があらたなロギングフレームワークlogbackを開発しているとのことです。

http://logging.apache.org/log4j/docs/
Logback Home

雰囲気としては、CVSSubversionのような別プロジェクトながら改善・改良を目指したもののようです。性能改善・フットプリント削減、モジュール化、

また、従来のJakarta Commonsのロギングに代わってSimple Logging Facade For Java(SLF4J)という複数のロギングAPIのラッパー(共通インタフェース)も開発しているようです。

今後は、log4jjakarta commons loggingは保守モードとなって(ユーザベースがあるので保守は必須)、新しい機能などはlogbackで取り入れられ、ロギングAPIのラッパーとして共通インタフェースにSLF4Jを使っていくのが流れなのかもしれません。