Конструкция try-catch-finally в Java. Finally выполняется всегда. Это знают все. А что если в finally стоит return, а в try или catch случился exception?
Вот такой примерчик. Предположим у нас есть наш собственный checked my.ArithmeticException которым мы оборачиваем стандартный unchecked ArithmeticException по каким-то своим причинам. Как-то так:
- int result = 0;
- try{
- result = a / b;
- }
- }
- finally{
- return result;
- }
Запускаем. Для входных параметров 12345 и 0 (деление на 0) наш exception не бросается а результат операции равен нулю.
Что же произошло? Почему отработал return а не throws?
Разберемся, как работает JVM при выходе из метода. Выход из метода - значение на стеке. И throws и return занимают её место, когда работа метода завершается. В нашем случае, в стек сначала попадает throws my.ArithmeticException, затем начинает работать finally который кладет на стек return и перетирает наш exception. Он теряется. Навсегда.
Как это может быть полезно? Да никак. Если уж очень надо чтобы метод всегда завершал работу штатно (например) , обычно пишут catch(Throwable) в котором хотя бы можно залогировать проблему перед тем как ее игнорировать. Finally + return затирает ВСЕ (да-да, и OutOfMemory тоже) exception'ы и вы никак об этом не узнаете.
Наверное единственный правильный вывод к которому я пришел - не писать return в finally.
И что же произойдет если выбрасывание OutOfMemory затрется? - сегментейшен флод :-D?
ОтветитьУдалитьВрядли. Скорее всего OutOfMemory выбросится еще раз, при дальнейшем исполнении текущим потоком.
ОтветитьУдалитьИнтересно в какой момент выбрасится повторно? При следующей попытки выделить память? А что если до этой попытки сборщик мусора уже освободил память?
ОтветитьУдалитьНасколько я знаю современные сборщики мусора (по крайней мере у HotSpot) помимо фоновых действий работают примерно так:
ОтветитьУдалитьВыделить память для нового объекта -> нет свободной памяти в new gen? -> вызвать GC -> памяти всё еще не хватает? -> вызвать FullGC -> памяти не хватает? -> OutOfMemory
Т.е. отвечая на ваш вопрос - да. Если другой поток между первый OutOfMemory и выделением нового объекта например успеет освободить пару объектов - тогда OutOfMemory не выбросится.
Спасибо за такое интерестное изложение проблемы. Но думаю многим читателям было бы интерестно увидеть собственно декомпиляцию байт кода этого примера=)
ОтветитьУдалитьУ finally немного другое предназначение:
ОтветитьУдалитьДопустим мы открываем какое-то соединение (TCP, UDP, SQL Connection или поток вывода PrintWriter и т.п.) в finally соединение можно закрыть!
PrintWriter out = null;
try{
out = response().getWriter();
//какой то код выкидывающий исключение
}catch(Exception e){}
finally{
if (out!= null)out.close();
}
В любом случае, поток вывода будет закрыт!