▪ It needn't get a synchronization lock when calling the getFactorial() method.
▪ It needn't store the field n in memory; it can keep that value in a register. Similarly it can
store the factorial object reference in a register.
▪ In fact, it needn't allocate an actual factorial object at all; it can just keep track of the in-
dividual fields of the object.
This kind of optimization is quite sophisticated: it is simple enough in this example, but these
optimizations are possible even with more complex code. Depending on the code usage, not
all optimizations will necessarily apply. But escape analysis can determine which of those
optimizations are possible and make the necessary changes in the compiled code.
Escape analysis is enabled by default. In rare cases, it will get things wrong, in which case
disabling it will lead to faster and/or more stable code. If you find this to be the case, then
simplifying the code in question is the best course of action: simpler code will compile bet-
ter. (It is a bug, however, and should be reported.)
1. Escape analysis is the most sophisticated of the optimizations the compiler can
perform. This is the kind of optimization that frequently causes microbenchmarks
to go awry.
2. Escape analysis can often introduce “bugs” into improperly synchronized code.
The discussion of the output of the PrintCompilation flag mentioned two cases where the
compiler deoptimized the code. Deoptimization means that the compiler had to “undo” some
previous compilation; the effect is that the performance of the application will be re-
duced—at least until the compiler can recompile the code in question.
There are two cases of deoptimization: when code is “made not entrant,” and when code is