One nice thing JITs can do that AOT compilers can't is on-stack replacement. That's where you recompile a particular function at run-time based on new information. This allows you do speculative optimizations.
For example, you might see that branch X is always taken. So you assume that X will always be true, and add a guard just in case which triggers a recompilation. You reoptimized the function on the basis of your new (speculative) information about X. This could improve register allocation, allow you remove lots of code (other branches maybe), inline functions, etc.
Java JITs have been known to inline hundreds of functions deep with this.
For example, you might see that branch X is always taken. So you assume that X will always be true, and add a guard just in case which triggers a recompilation. You reoptimized the function on the basis of your new (speculative) information about X. This could improve register allocation, allow you remove lots of code (other branches maybe), inline functions, etc.
Java JITs have been known to inline hundreds of functions deep with this.