Я думаю, що інші зробили гарну роботу, пояснивши, чому cnt> 0, але недостатньо деталей щодо того, чому cnt = 4, і чому cnt змінюється настільки сильно серед різних налаштувань. Я спробую заповнити цю порожнечу тут.
Дозволяти
- X - загальний розмір стека
- Я повинен бути простором стеку, який використовується під час першого введення основного
- R буде збільшуватися простір стеку щоразу, коли ми входимо в основний
- P - простір стека, необхідний для запуску
System.out.println
Коли ми вперше потрапимо в основний, простір, що залишився, - це XM. Кожен рекурсивний дзвінок займає більше R пам'яті. Отже, для 1 рекурсивного виклику (на 1 більше, ніж оригінальний) використання пам'яті M + R. Припустимо, що StackOverflowError кидається після успішних рекурсивних викликів C, тобто M + C * R <= X і M + C * (R + 1)> X. На час першого StackOverflowError залишилося пам'ять X - M - C * R.
Щоб мати змогу запустити System.out.prinln, нам потрібно P кількість місця, що залишилося в стеку. Якщо так сталося, що X - M - C * R> = P, тоді буде надруковано 0. Якщо P вимагає більше місця, тоді ми видаляємо кадри зі стека, набуваючи R пам'яті ціною cnt ++.
Коли printlnнарешті вдасться запустити, X - M - (C - cnt) * R> = P. Отже, якщо P великий для певної системи, то cnt буде великим.
Давайте розглянемо це на деяких прикладах.
Приклад 1: Припустимо
- X = 100
- M = 1
- R = 2
- Р = 1
Тоді C = пол ((XM) / R) = 49, а cnt = стеля ((P - (X - M - C * R)) / R) = 0.
Приклад 2: Припустимо, що
- X = 100
- M = 1
- R = 5
- Р = 12
Тоді C = 19, а cnt = 2.
Приклад 3: Припустимо, що
- X = 101
- M = 1
- R = 5
- Р = 12
Тоді C = 20, а cnt = 3.
Приклад 4: Припустимо, що
- X = 101
- M = 2
- R = 5
- Р = 12
Тоді C = 19, а cnt = 2.
Таким чином, ми бачимо, що і система (M, R, і P), і розмір стека (X) впливають на cnt.
Як бічна примітка, не важливо, скільки місця catchпотрібно для початку. Поки не буде достатньо місця для catch, тоді cnt не збільшиться, тому немає зовнішніх ефектів.
EDIT
Я повертаю назад те, про що говорив catch. Це відіграє певну роль. Припустимо, для запуску потрібна кількість T місця. cnt починає збільшуватись, коли простір, що залишився, перевищує T іprintln запускається, коли простір, що залишився, перевищує T + P. Це додає додатковий крок до обчислень та додатково замулює вже мутний аналіз.
EDIT
Я нарешті знайшов час провести кілька експериментів, щоб підкріпити свою теорію. На жаль, теорія, схоже, не відповідає експериментам. Те, що насправді відбувається, дуже відрізняється.
Налаштування експерименту: сервер Ubuntu 12.04 з типовою java та jdk за замовчуванням. Xss починається з 70 000 з кроком 1 байт до 460 000.
Результати доступні за посиланням: https://www.google.com/fusiontables/DataSource?docid=1xkJhd4s8biLghe6gZbcfUs3vT5MpS_OnscjWDbM
Я створив іншу версію, де видаляється кожна повторена точка даних. Іншими словами, показані лише точки, які відрізняються від попередніх. Це полегшує бачити аномалії. https://www.google.com/fusiontables/DataSource?docid=1XG_SRzrrNasepwZoNHqEAKuZlHiAm9vbEdwfsUA