Simulation is typically ended by calling
$finish system task in traditional SystemVerilog testbenches. UVM has introduced many phases that aid in a very structural methodology for building testbench environments and applying stimulus. These phases are grouped into three main categories - build and connect, run-time and clean up.
Build-connect and clean-up phases are executed in zero time and do not cause any advancement in simulation. However, run-time phases consume simulation time. Each phase ends when there are no more pending objections for that phase. The end of test happens when all its time consuming phases come to an end.
As you already know, testbench components step through each phase during the course of a simulation and execution does not move to the next phase unless all components in the current phase are done. The way this is achieved is through phase objections. Simply put, each component raises a flag as soon as they enter the phase which indicates that they are currently busy with something and the flag is returned as soon as the component is done with what it was doing. So unless all components have indicated that they are finished, by dropping the flag, the current phase will continue. A more formal word for the flag is an objection.
UVM testbench components which uses an objection mechanism share a counter between them. The method
raise_objection increments the count and
drop_objection decrements the count. Once the value of the shared counter reaches zero from a higher value, it indicates that all participating components have dropped their objections and the phase is ready to end.