É caro usar blocos try-catch mesmo que uma exceção nunca seja lançada?

Sabemos que é caro pegar exceções. Mas, também é caro usar um bloco try-catch em Java mesmo que uma exceção nunca seja lançada?

Eu encontrei a pergunta / resposta do Stack Overflow Por que os blocos try são caros? , mas é para o .net

try quase não tem despesa nenhuma. Em vez de fazer o trabalho de configurar o try em tempo de execução, os metadados do código são estruturados em tempo de compilation de forma que quando uma exceção é lançada, ele faz uma operação relativamente cara de subir a pilha e ver se existem blocos try que pegar essa exceção. Da perspectiva de um leigo, try também ser livre. Na verdade, está jogando a exceção que lhe custa – mas, a menos que você esteja jogando centenas ou milhares de exceções, ainda não notará o custo.


try tem alguns custos menores associados a ele. Java não pode fazer algumas otimizações no código em um bloco try que ele faria de outra forma. Por exemplo, o Java frequentemente reorganiza as instruções em um método para torná-lo mais rápido – mas o Java também precisa garantir que, se uma exceção for lançada, a execução do método é observada como se suas instruções, escritas no código-fonte, fossem executadas em ordem até alguma linha.

Porque em um bloco try uma exceção pode ser lançada (em qualquer linha do bloco try! Algumas exceções são lançadas de forma assíncrona, chamando stop em um Thread (que é obsoleto), e mesmo além disso o OutOfMemoryError pode acontecer em quase qualquer lugar) e ainda assim, ele pode ser capturado e o código continuar a ser executado posteriormente no mesmo método, é mais difícil raciocinar sobre as otimizações que podem ser feitas, portanto, é menos provável que isso aconteça. (Alguém teria que programar o compilador para fazê-las, raciocinar e garantir a exatidão, etc. Seria uma grande dor para algo ser “excepcional”). Mas, novamente, na prática, você não notará coisas como essa.

Vamos medir isso, vamos?

 public abstract class Benchmark { final String name; public Benchmark(String name) { this.name = name; } abstract int run(int iterations) throws Throwable; private BigDecimal time() { try { int nextI = 1; int i; long duration; do { i = nextI; long start = System.nanoTime(); run(i); duration = System.nanoTime() - start; nextI = (i < < 1) | 1; } while (duration < 100000000 && nextI > 0); return new BigDecimal((duration) * 1000 / i).movePointLeft(3); } catch (Throwable e) { throw new RuntimeException(e); } } @Override public String toString() { return name + "\t" + time() + " ns"; } public static void main(String[] args) throws Exception { Benchmark[] benchmarks = { new Benchmark("try") { @Override int run(int iterations) throws Throwable { int x = 0; for (int i = 0; i < iterations; i++) { try { x += i; } catch (Exception e) { e.printStackTrace(); } } return x; } }, new Benchmark("no try") { @Override int run(int iterations) throws Throwable { int x = 0; for (int i = 0; i < iterations; i++) { x += i; } return x; } } }; for (Benchmark bm : benchmarks) { System.out.println(bm); } } } 

No meu computador, isso imprime algo como:

 try 0.598 ns no try 0.601 ns 

Pelo menos neste exemplo trivial, a instrução try não teve impacto mensurável no desempenho. Sinta-se à vontade para medir os mais complexos.

Em geral, recomendo não se preocupar com o custo de desempenho de construções de linguagem até que você tenha evidência de um problema real de desempenho em seu código. Ou, como disse Donald Knuth: "otimização prematura é a raiz de todo mal".

try / catch pode ter algum impacto no desempenho. Isso porque evita que a JVM faça algumas otimizações. Joshua Bloch, em “Effective Java”, disse o seguinte:

• Colocar código dentro de um bloco try-catch inibe certas otimizações que implementações de JVM modernas poderiam desempenhar.

Sim, como os outros disseram, um bloco try inibe algumas otimizações nos {} caracteres que o cercam. Em particular, o otimizador deve assumir que uma exceção poderia ocorrer em qualquer ponto dentro do bloco, portanto, não há garantia de que as instruções sejam executadas.

Por exemplo:

  try { int x = a + b * c * d; other stuff; } catch (something) { .... } int y = a + b * c * d; use y somehow; 

Sem a try , o valor calculado para atribuir a x poderia ser salvo como uma “subexpressão comum” e reutilizado para ser atribuído a y . Mas, por causa da try não há garantia de que a primeira expressão tenha sido avaliada, portanto, a expressão deve ser recalculada. Isso geralmente não é um grande problema no código “linear”, mas pode ser significativo em um loop.

Deve-se notar, no entanto, que isso se aplica APENAS ao código JITCed. O javac faz apenas uma pequena quantidade de otimização, e há um custo zero para o intérprete do bytecode entrar / sair de um bloco try . (Não há bytecodes gerados para marcar os limites do bloco.)

E para bestsss:

 public class TryFinally { public static void main(String[] argv) throws Throwable { try { throw new Throwable(); } finally { System.out.println("Finally!"); } } } 

Saída:

 C:\JavaTools>java TryFinally Finally! Exception in thread "main" java.lang.Throwable at TryFinally.main(TryFinally.java:4) 

saída javap:

 C:\JavaTools>javap -c TryFinally.class Compiled from "TryFinally.java" public class TryFinally { public TryFinally(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: return public static void main(java.lang.String[]) throws java.lang.Throwable; Code: 0: new #2 // class java/lang/Throwable 3: dup 4: invokespecial #3 // Method java/lang/Throwable."":()V 7: athrow 8: astore_1 9: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 12: ldc #5 // String Finally! 14: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 17: aload_1 18: athrow Exception table: from to target type 0 9 8 any } 

Não “GOTO”.

Ainda outro microbenchmark ( fonte ).

Eu criei um teste no qual meço a versão do código try-catch e no-try-catch com base em uma porcentagem de exceção. Porcentagem de 10% significa que 10% dos casos de teste tinham divisão por zero. Em uma situação, ele é tratado por um bloco try-catch e, por outro, por um operador condicional. Aqui está minha tabela de resultados:

 OS: Windows 8 6.2 x64 JVM: Oracle Corporation Java HotSpot(TM) 64-Bit Server VM 23.25-b01 
 Percentagem |  Resultado (try / if, ns)   
     0% |  88/90   
     1% |  89/87    
     10% |  86/97    
     90% |  85/83   

O que diz que não há diferença significativa entre nenhum desses casos.

Para entender por que as otimizações não podem ser realizadas, é útil entender os mecanismos subjacentes. O exemplo mais sucinto que encontrei foi implementado em macros C em: http://www.di.unipi.it/~nids/docs/longjump_try_trow_catch.html

 #include  #include  #define TRY do{ jmp_buf ex_buf__; switch( setjmp(ex_buf__) ){ case 0: while(1){ #define CATCH(x) break; case x: #define FINALLY break; } default: #define ETRY } }while(0) #define THROW(x) longjmp(ex_buf__, x) 

Compiladores geralmente têm dificuldade em determinar se um salto pode ser localizado em X, Y e Z, então eles pularam as otimizações que não podem garantir, mas a implementação em si é bastante leve.