Typically tracing VMs go through various phases when they execute a program: Interpretation/profiling, tracing, code generation and execution of the generated code. When the program starts, everything is interpreted. The inter- preter does lightweight profiling to establish which loops are run most frequently. This lightweight profiling is usually done by having a counter on each backward jump instruc- tion that counts how often this particular backward jump is executed. Since loops need a backward jump somewhere, this method looks for loops in the user program.
Taken from a paper about PyPy’s tracing JIT written by the developers of PyPy. The paper explains the technique behind the tracing JIT very well. Read it if you want to know the gory details behind PyPy.