Мне было интересно, что происходит, когда вы пытаетесь поймать StackOverflowError, и я придумал следующий метод:
class RandomNumberGenerator {
static int cnt = 0;
public static void main(String[] args) {
try {
main(args);
} catch (StackOverflowError ignore) {
System.out.println(cnt++);
}
}
}
Теперь мой вопрос:
Почему этот метод выводит «4»?
Я подумал, может быть, это потому, что System.out.println()
в стеке вызовов нужно 3 сегмента, но я не знаю, откуда взялось число 3. Когда вы смотрите на исходный код (и байт-код) System.out.println()
, это обычно приводит к гораздо большему количеству вызовов методов, чем 3 (так что 3 сегмента в стеке вызовов будет недостаточно). Если это из-за оптимизации, которую применяет виртуальная машина Hotspot (встраивание метода), мне интересно, будет ли результат отличаться на другой виртуальной машине.
Редактировать :
Поскольку результат, похоже, сильно зависит от JVM, я получаю результат 4 с использованием
Java (TM) SE Runtime Environment (сборка 1.6.0_41-b02
) 64-разрядной серверной виртуальной машины Java HotSpot (TM) (сборка 20.14-b01, смешанный режим)
Объяснение, почему я думаю, что этот вопрос отличается от понимания стека Java :
Мой вопрос не в том, почему существует cnt> 0 (очевидно, потому что System.out.println()
требуется размер стека и бросает другое, StackOverflowError
прежде чем что-то будет напечатано), а почему у него конкретное значение 4, соответственно 0,3,8,55 или что-то еще на другом системы.
источник
5
,6
и38
с Java 1.7.0_10Ответы:
Я думаю, что другие хорошо поработали, объяснив, почему cnt> 0, но недостаточно подробностей относительно того, почему cnt = 4 и почему cnt так сильно различается в разных настройках. Я попытаюсь заполнить эту пустоту здесь.
Позволять
System.out.println
Когда мы впервые попадаем в main, остается пространство 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: предположим
Тогда C = floor ((XM) / R) = 49 и cnt = потолок ((P - (X - M - C * R)) / R) = 0.
Пример 2: Предположим, что
Тогда C = 19 и cnt = 2.
Пример 3: Предположим, что
Тогда C = 20 и cnt = 3.
Пример 4: Предположим, что
Тогда C = 19 и cnt = 2.
Таким образом, мы видим, что и система (M, R и P), и размер стека (X) влияют на cnt.
Кстати, неважно, сколько места
catch
требуется для запуска. Пока нет места дляcatch
, cnt не будет увеличиваться, поэтому внешние эффекты отсутствуют.РЕДАКТИРОВАТЬ
Я забираю то, о чем говорил
catch
. Это действительно играет роль. Предположим, для запуска требуется T свободного места. cnt начинает увеличиваться, когда оставшееся пространство больше T, иprintln
запускается, когда оставшееся пространство больше, чем T + P. Это добавляет дополнительный шаг к вычислениям и еще больше затрудняет и без того грязный анализ.РЕДАКТИРОВАТЬ
Я наконец нашел время, чтобы провести несколько экспериментов, чтобы подтвердить свою теорию. К сожалению, теория, похоже, не соответствует экспериментам. На самом деле происходит совсем другое.
Настройка эксперимента: сервер Ubuntu 12.04 с java по умолчанию и default-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
источник
StackOverflowError
выброса и как это влияет на результат. Если он содержит только ссылку на кадр стека в куче (как предположил Джей), то вывод должен быть вполне предсказуемым для данной системы.Это жертва плохого рекурсивного вызова. Вам интересно, почему значение cnt меняется, потому что размер стека зависит от платформы. Java SE 6 в Windows имеет размер стека по умолчанию 320 КБ для 32-разрядной виртуальной машины и 1024 КБ для 64-разрядной виртуальной машины. Вы можете прочитать здесь .
Вы можете работать с разными размерами стека, и вы увидите разные значения cnt до переполнения стека -
Вы не видите, что значение cnt печатается несколько раз, даже если иногда значение больше 1, потому что ваш оператор печати также выдает ошибку, которую вы можете отладить, чтобы быть уверенным, через Eclipse или другие IDE.
Вы можете изменить код на следующий, чтобы отлаживать выполнение каждого оператора, если хотите:
ОБНОВИТЬ:
Поскольку этому уделяется гораздо больше внимания, давайте рассмотрим еще один пример, чтобы прояснить ситуацию -
Мы создали еще один метод с именем overflow, чтобы выполнить некорректную рекурсию, и удалили оператор println из блока catch, чтобы он не запускал другой набор ошибок при попытке печати. Это работает, как ожидалось. Вы можете попробовать поставить System.out.println (cnt); оператор после cnt ++ выше и скомпилировать. Затем выполните несколько раз. В зависимости от вашей платформы вы можете получить разные значения cnt .
Вот почему обычно мы не выявляем ошибок, потому что тайна кода - это не фантастика.
источник
Поведение зависит от размера стека (который можно установить вручную с помощью
Xss
. Размер стека зависит от архитектуры. Из исходного кода JDK 7 :Таким образом, когда
StackOverflowError
вызывается, ошибка перехватывается в блоке catch. Вотprintln()
еще один вызов стека, который снова вызывает исключение. Это повторяется.Сколько раз это повторяется? - Ну, это зависит от того, когда JVM считает, что это больше не stackoverflow. И это зависит от размера стека каждого вызова функции (трудно найти) и размера
Xss
. Как упоминалось выше, общий размер по умолчанию и размер каждого вызова функции (зависит от размера страницы памяти и т. Д.) Зависит от платформы. Отсюда разное поведение.Звонок по
java
телефону-Xss 4M
дает мне41
. Отсюда корреляция.источник
catch
- это блок, который занимает память в стеке. Неизвестно, сколько памяти занимает каждый вызов метода. Когда стек очищается, вы добавляете еще один блокcatch
и так далее. Это может быть поведение. Это только предположения.Я думаю, что отображаемое число - это количество раз, когда
System.out.println
вызов вызываетStackoverflow
исключение.Вероятно, это зависит от реализации
println
и количества вызовов стекирования, которые он выполняет.В качестве иллюстрации:
main()
Запуск вызоваStackoverflow
исключение в I Call. Вызов i-1 из main перехватывает исключение иprintln
вызывает второйStackoverflow
.cnt
получить приращение до 1. Вызов i-2 основного улова теперь является исключением и вызовомprintln
. Вprintln
методе вызывается 3-е исключение.cnt
получить приращение до 2. это продолжается до тех пор, покаprintln
не будет выполнен весь необходимый вызов и, наконец, отобразится значениеcnt
.Тогда это зависит от фактической реализации
println
.Для JDK7 либо он обнаруживает циклический вызов и генерирует исключение раньше, либо сохраняет некоторый ресурс стека и генерирует исключение до достижения предела, чтобы дать некоторое пространство для логики исправления, либо
println
реализация не выполняет вызовы, либо операция ++ выполняется послеprintln
таким образом , вызов байпас по исключению.источник
main
рекурсивно повторяется до тех пор, пока не переполнит стек на глубине рекурсииR
.R-1
.R-1
Оценивается блок catch на глубине рекурсииcnt++
.R-1
вызововprintln
, размещаяcnt
«S старого значения в стеке.println
будет внутренне вызывать другие методы и использовать локальные переменные и прочее. Все эти процессы требуют места в стеке.println
требуется пространство стека, новое переполнение стека запускается на глубине,R-1
а не на глубинеR
.R-2
.R-3
.R-4
.R-5
.println
завершения (обратите внимание, что это деталь реализации, она может отличаться).cnt
был пост-приращение на глубинахR-1
,R-2
,R-3
,R-4
, и , наконец,R-5
. Пятый постинкремент вернул четыре, что и было напечатано.main
успешном завершении на глубинеR-5
весь стек раскручивается без запуска дополнительных блоков catch, и программа завершается.источник
Покопавшись некоторое время, я не могу сказать, что нашел ответ, но думаю, что сейчас он довольно близок.
Во-первых, нам нужно знать, когда
StackOverflowError
будет брошено a . Фактически, стек для потока Java хранит кадры, которые содержат все данные, необходимые для вызова метода и возобновления. Согласно спецификациям языка Java для JAVA 6 , при вызове методаВо-вторых, мы должны прояснить, что означает « недостаточно памяти для создания такого кадра активации ». Согласно спецификациям виртуальной машины Java для JAVA 6 ,
Таким образом, при создании кадра должно быть достаточно места в куче для создания кадра стека и достаточно места в стеке для хранения новой ссылки, которая указывает на новый кадр стека, если кадр выделен в куче.
А теперь вернемся к вопросу. Из вышесказанного мы можем знать, что когда метод выполняется, он может просто стоить столько же места в стеке. И для вызова
System.out.println
(может) требуется 5 уровней вызова метода, поэтому необходимо создать 5 кадров. Затем, когдаStackOverflowError
он выброшен, он должен вернуться 5 раз, чтобы получить достаточно места в стеке для хранения ссылок на 5 кадров. Следовательно, распечатывается 4. Почему не 5? Потому что вы используетеcnt++
. Измените его на++cnt
, и тогда вы получите 5.И вы заметите, что когда размер стека повышается, вы иногда получаете 50. Это связано с тем, что тогда необходимо учитывать объем доступного пространства кучи. Если размер стека слишком велик, возможно, перед стеком закончится место в куче. И (возможно) фактический размер фреймов стека
System.out.println
примерно в 51 раз большеmain
, поэтому он возвращается 51 раз и выводит 50.источник
System.out.print
или стратегию выполнения метода. Как описано выше, реализация виртуальной машины также влияет на место фактического хранения кадра стека.Это не совсем ответ на вопрос, но я просто хотел добавить кое-что к исходному вопросу, с которым я столкнулся, и тому, как я понял проблему:
В исходной задаче исключение перехватывается там, где это было возможно:
Например с jdk 1.7 он отлавливается по первому месту возникновения.
но в более ранних версиях jdk похоже, что исключение не обнаруживается в первом месте возникновения, следовательно, 4, 50 и т. д.
Теперь, если вы удалите блок try catch следующим образом
Затем вы увидите все значения и
cnt
созданные исключения (на jdk 1.7).Я использовал netbeans для просмотра вывода, так как cmd не будет отображать весь вывод и исключение.
источник