mike-neckのブログ

Java or Groovy or Swift or Golang

Streamパイプラインを1つ生成するのに必要なメモリを求めてみる

元になった記事はこれ。

mike-neck.hatenadiary.com

パイプライン一つ一つがリンクドリスト状のStreamインスタンスで構成されているということで、どれくらいの長さのストリームパイプラインを書くとOutOfMemoryErrorが発生するのか試してみようと思った。

コードはこれ

import java.util.Map;
import java.util.HashMap;
import java.util.stream.IntStream;
import java.text.DecimalFormat;

public class StreamMemorySample {
    public static void main(String[] args) {
        Runtime rt = Runtime.getRuntime();
        Map<Integer, IntStream> streams = new HashMap<>();
        for(int i = 0; i < 10; i++) {
            streams.put(i, IntStream.of(i));
        }
        long before = rt.maxMemory() - rt.freeMemory();
        for (long i = 0; i < 1_000_000_000; i++) {
            final int v = (int)(i / 10);
            int key = (int)(i % 10);
            try {
                streams.computeIfPresent(key, (k, s) -> s.filter(n -> n < v));
            } catch(OutOfMemoryError e) {
                System.out.println("");
                System.out.println(i);
                throw e;
            }
            if(i > 0 && i % 1_000_000 == 0) {
                long free = rt.freeMemory();
                long max = rt.maxMemory();
                String pip = new DecimalFormat("#,##0").format(i);
                String used = new DecimalFormat("#,##0").format(max - free);
                String fr = new DecimalFormat("#,##0").format(free);
                String one = new DecimalFormat("#,##0").format((max - free - before) / 1_000_000);
                System.out.printf("\npipe line[%12s] -> used[%12s], per one[%12s], free memory [%12s]\n",
                        pip, used, one, fr);
                rt.gc();
                before = max - free;
            }
        }
    }
}

出力はこんな感じ

$ java -Xms512M -Xmx512M StreamMemorySample
pipe line[   1,000,000] -> used[  91,626,464], per one[          86], free memory [ 423,224,352]
pipe line[   2,000,000] -> used[ 179,085,568], per one[          87], free memory [ 335,765,248]
pipe line[   3,000,000] -> used[ 242,269,736], per one[          63], free memory [ 272,581,080]
pipe line[   4,000,000] -> used[ 323,051,256], per one[          80], free memory [ 191,799,560]
pipe line[   5,000,000] -> used[ 400,976,640], per one[          77], free memory [ 113,874,176]
pipe line[   6,000,000] -> used[ 481,266,056], per one[          80], free memory [  33,584,760]

6143117
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
        at java.util.stream.IntPipeline.filter(Unknown Source)
        at StreamMemorySample.lambda$main$1(StreamMemorySample.java:18)
        at StreamMemorySample$$Lambda$1/2055281021.apply(Unknown Source)
        at java.util.HashMap.computeIfPresent(Unknown Source)
        at StreamMemorySample.main(StreamMemorySample.java:18)

だいたい1つにつき80バイトくらいつかっていて、512M程度与えられていれば610万回くらいはストリームパイプラインつなげても大丈夫そうです。

ただ、そんなに並べると、終端操作呼び出しでスタックオーバーフローが発生します。

Exception in thread "main" java.lang.StackOverflowError
    at java.util.stream.IntPipeline$9$1.begin(Unknown Source)