Yes, of course with a JIT you can do much better than an AOT compiler, and for dynamic languages is pretty much required to get reasonable performance.
Well, compared to profile guided optimization as mentioned by the other commenter earlier, that's really only the case if the profile of called methods vary greatly between runs.
The polymorphic inline caching from Self for example is guided by collecting simple stats. Tracing does the same. A JIT ensures those stats are always completely up to date, but nothing stops you from saving it and using it for an AOT compiler as well.
But often even that is overkill, as you can often statically deduce a lot about the types a method is likely to get called with by simply looking at the call sites, and most programs have very static call profiles.
The polymorphic inline caching from Self for example is guided by collecting simple stats. Tracing does the same. A JIT ensures those stats are always completely up to date, but nothing stops you from saving it and using it for an AOT compiler as well.
But often even that is overkill, as you can often statically deduce a lot about the types a method is likely to get called with by simply looking at the call sites, and most programs have very static call profiles.