In order to focus more on throwing costs, we will use the static exception, thereby paying upfront the costs of creating the stack trace. There, we can make a few throwing tests . In hindsight, we do two tests: one with the default settings, and another with inlining completely turned off ( -XX:-Inline ).

Here is where it gets seriously more complicated. After the exception is created, it is usually thrown. Throwing the exception in theory requires non-local control transfer, because the exception handler can be somewhere up the call stack. In practice, however, the target handler can be just in the same compilation unit, especially after inlining. But before we go there, let us do some basic tests.

Results

Running this on my i5 (down-clocked to 2.0 Ghz), Linux/x86_64, JDK 7u40 will yield:

Benchmark Mode Samples Mean Mean error Units ThrowingBench.exception_inline avgt 25 4.166 0.008 ns/op ThrowingBench.exception_noInline avgt 25 248.034 1.160 ns/op ThrowingBench.plain_inline avgt 25 1.265 0.007 ns/op ThrowingBench.plain_noInline avgt 25 6.478 0.009 ns/op

The costs for turning off the inlining for plain methods are expected: a few nanoseconds here and there for two non-inlined calls on the hotpath (first one is the benchmark method itself, the second is its callee). Now what is going on with exception_noInline ? It is actually educational at this point to dig into the assembly to see how exception handling works at a very basic level.

Consider the generated assembly for exception_inline first (I rearranged and added the comments, purged the non-essential addresses, etc):

; {method} 'exception_inline' '()I' in 'net/shipilev/perf/exceptions/ThrowingBench' ; (...skipped...) ; Verified Entry Point mov %eax,-0x14000(%rsp) push %rbp sub $0x10,%rsp ;*getfield staticException mov 0x10(%rsi),%r11d ;*getfield metadata ; implicit exception: dispatches to $HANDLER mov 0x20(%r12,%r11,8),%eax add $0x10,%rsp pop %rbp ; safepoint poll, and return! test %eax,0x9e40280(%rip) retq ; exception handler HANDLER: mov $0xfffffff6,%esi nop ; calling runtime to raise the exception callq 0x00007f76047eb320 ; OopMap{off=76} callq 0x00007f760d3eeb10 ;*athrow ; do not expect to return, halt otherwise hlt

What do we see here? Well, there is an exception handler lurking. However, this is not our exception handler, that is an implicit null check for staticException field. The common path loads staticException.metadata into %eax and almost immediately returns. Recall, that the x86_64 calling convention puts the returned value into the %eax . Note that even though there is the try { } catch { } section in the original Java code, as far as compiler is concerned, this is just the form of control flow which can be optimized to straightest-forward code. This part is easy, because the target for the control transfer is right here since s1() is inlined.

Let’s break that, and see what the generated assembly for exception_noInline looks like.

; {method} 'exception_noInline' '()I' in 'net/shipilev/perf/exceptions/ThrowingBench' ; (...skipped...) ; Verified Entry Point mov %eax,-0x14000(%rsp) push %rbp sub $0x10,%rsp ; calling s1() xchg %ax,%ax callq 0x00007f5c981a7b60 ; OopMap{off=52} HERE_IS_YOUR_VALUE_SIR: add $0x10,%rsp pop %rbp ; poll safepoint and return! test %eax,0x9f136e1(%rip) retq ; EXCEPTION HANDLER ; %eax is exception; typecheck for LilException mov %rax,%r10 mov 0x8(%rax),%r11d cmp $0xe0449fb9,%r11d jne TYPE_CHECK_FAILED ; *getfield metadata mov 0x20(%r10),%eax jmp HERE_IS_YOUR_VALUE_SIR ; type-check failed, unhandled exception, drop the frame ; and get to VM crying the tears of evil, hoping somebody ; else will handle TYPE_CHECK_FAILED: mov %rax,%rsi add $0x10,%rsp pop %rbp jmpq 0x00007f5c981d23a0 ; expecting no return hlt

What do we see now? As expected, the call to s1() is not inlined. But we are expected to receive the exception from there, right? Also, we must get the metadata out of the exception and return it. Here is how it works: the exception handler is right there in the method, its code generated just after the final ret . It is a VM exception handler duty to pass control here if somebody past the s1() call throws the exception back to us. If such a throw occurs, we read out metadata into %eax , jump right after the call pretending nothing happened, and return as usual, concluding the receiver part of the story.

And here is the shiny s1() itself:

; {method} 's1' '()I' in 'net/shipilev/perf/exceptions/ThrowingBench' ; (..skipped...) ; Verified Entry Point mov %eax,-0x14000(%rsp) push %rbp sub $0x10,%rsp ; getfield staticException mov 0x10(%rsi),%r11d ; check for null and pass on test %r11d,%r11d je HOLY_CRAP_THIS_CANT_BE_HAPPENING ; everything is fine. ; unpack the compressed pointer to the exception? lea (%r12,%r11,8),%rsi ; leave the frame and transfer to VM add $0x10,%rsp pop %rbp jmpq 0x00007f5c981d23a0 ; the exception is null, call around screaming and throwing NPEs HOLY_CRAP_THIS_CANT_BE_HAPPENING: mov $0xfffffff6,%esi xchg %ax,%ax callq 0x00007f5c981a9320 callq 0x00007f5ca0e84b10 ; expect no return hlt