Runtime#freeMemory() は正しい値を返さない

Runtime#freeMemory() に関して勘違いしていたのかも。

public class Memory {
    public static void main(String[] args) throws Exception {
        Runtime runtime = Runtime.getRuntime();
        java.util.List<Object> list = new java.util.LinkedList<Object>();
        for (int i = 0; i < 10; i++) {
            list.add(new Object());
            System.out.print(i + " total:" + runtime.totalMemory()
                + " free:" + runtime.freeMemory()
                + " max:" + runtime.maxMemory() + "\n");
        }
    }
}

上記コードで期待する動作は、ループ内でオブジェクトが生成されているので、free: に表示される空きメモリの数値がだんだん減っていく、というもの。

しかし、実際には、以下のように同じだった。

$ /usr/lib/jvm/java-1.5.0-sun-1.5.0.19/bin/java Memory
0 total:189005824 free:188018832 max:954466304
1 total:189005824 free:188018832 max:954466304
2 total:189005824 free:188018832 max:954466304
3 total:189005824 free:188018832 max:954466304
4 total:189005824 free:188018832 max:954466304
5 total:189005824 free:188018832 max:954466304
6 total:189005824 free:188018832 max:954466304
7 total:189005824 free:188018832 max:954466304
8 total:189005824 free:188018832 max:954466304
9 total:189005824 free:188018832 max:954466304

Sun Java 1.5、Sun Java 1.6 OpenJDK 1.6、Oracle Java 1.7 で確認したが、どれも同じ結果。

gcj だと 2 回目で減ったものの、しかし同じだった。

$ /usr/lib/jvm/java-1.5.0-gcj-4.4/bin/java Memory
0 total:479232 free:53248 max:9223372036854775807
1 total:479232 free:49152 max:9223372036854775807
2 total:479232 free:49152 max:9223372036854775807
3 total:479232 free:49152 max:9223372036854775807
4 total:479232 free:49152 max:9223372036854775807
5 total:479232 free:49152 max:9223372036854775807
6 total:479232 free:49152 max:9223372036854775807
7 total:479232 free:49152 max:9223372036854775807
8 total:479232 free:49152 max:9223372036854775807
9 total:479232 free:49152 max:9223372036854775807

関係ないけど gcj の maxMemory の数値、半端ないなぁ。

Javadoc の freeMemory() の説明は:

Java 仮想マシン内の空きメモリーの量を返します。gc メソッドを呼び出すと、freeMemory によって返される値が増える場合があります。

freeMemory

と書かれていて、gc で値が変わるとは書かれているものの、オブジェクトアロケートで値が変わるとは一言も書かれていない。試しにループ内に gc 呼び出しを入れてみたら、それっぽい結果になった。

0 total:189005824 free:187888416 max:954466304
1 total:189005824 free:187888232 max:954466304
2 total:189005824 free:187888176 max:954466304
3 total:189005824 free:187888240 max:954466304
4 total:189005824 free:187888184 max:954466304
5 total:189005824 free:187888128 max:954466304
6 total:189005824 free:187888072 max:954466304
7 total:189005824 free:187888016 max:954466304
8 total:189005824 free:187887960 max:954466304
9 total:189005824 free:187887904 max:954466304

Oracle Java や OpenJDK の実装を見ていないのでどういう風になっているかは確認できていないんだけど、gc のタイミングでメモリ使用状況を更新している、ということなんだろうか。

Runtime#xxxMemory() メソッドは、ずっと呼び出した瞬間のメモリ使用状況を返してくれるものだと思い込んでいたけど、これは勘違いだったということなのか。少なくとも、そのようにはなっていないので、今後気をつけるようにしようと思う。

追記

どうやらブロック単位でヒープが管理されているので、10 回程度のループではブロックが消費されないので freeMemory() の返す値は変わらない、ということらしい。