Early version of Unix did this. Do access checking on each memory reference. Give each program a piece of memory that it can access, and on every memory reference check that it stays within its address space. When OS runs process, loads the base and bounds registers for that process. Cray-1 did this.
Program generates virtual addresses that get translated into physical addresses.
If it tries to access it, the hardware will generate an exception. End up with a model where physical memory of machine is dynamically allocated to processes as they enter and exit the system. All suffer from external fragmentation. In worst case, may have enough memory free to load a process, but can't use it because it is fragmented into little pieces. Either because of fragmentation or because physical memory is too small to hold all address spaces.