Current common solution is to use an MMU, memory management unit. No need to think only Intel or arm.
You can look for the terms virtual memory and physical memory although there is a problem with the use of the term virtual memory.
Physical memory is the processors address space from 0x000...0000 to 0xFFF...FFF however many bits of address.
Virtual memory does not require a separate processor mode but in general implementations do and this allows for isolate between the kernel (the OS if you will) and the application(s). At the core address bus between the processor and the mmu an id is presented as well as the address and data. The operating system sets up mmu tables which define a chunk of virtual memory and describes the physical address. So the virtual address chunk of 16K bytes at 0x00000000 for a specific application may map to 0x12300000 in physical memory. For that same application 0x00004000 may map to 0x32100000 and so on, this makes memory allocation much easier for the operating system, if you wanted to allocate a megabyte of memory it doesn't have to find a linear/aligned chunk of free memory but can build it out of smaller chunks of unallocated/free memory. This among other things allows the application to think it has access to a large portion of the processors memory space.
There are different design implementations, but for protection between the OS and application the id that is used on the bus distinguishes between the applications and OS. If the bus transaction contains the combination of an id and an address that id does not have access to (each chunk has access/protection bits to indicate in some form if an id has access to that virtual address) then the mmu generates a fault which is some sort of an exception/interrupt to the processor in a processor specific way that switches the processor to the protected/kernel mode and hits an interrupt/exception handler.
This is not necessarily a bad thing. For example when running a virtual machine instead of an application the virtual machine software could intentionally be designed such that a particular virtual address is an emulation of some peripheral, an Ethernet controller for example so the VM can have access to the network. When the application hits that address the fault happens, but instead of shutting down the application and notifying the user there was a problem, you instead based on that address emulate the peripheral by reacting to or returning a result back to the application that the application cant tell from a real peripheral. Another feature of faults is the layman's (not programmer / software/hardware engineer) version of virtual memory.
And this is where your application could think that it has access to all of the computers memory. The application(s) may have used up all the free memory (RAM) in the system. But within their virtual address spaces none of them have actually done that, at one point an application may have had physical 0x11100000 allocated to virtual 0x20000000, but there is a demand on the system for an allocation of memory and there is no more available. The operating system can use an algorithm to decide that this application has not used its space for a while or more likely a randomized lottery and takes the chunk at 0x11100000 physical and copies its contents to a hard drive/(non ram storage), marks virtual 0x20000000 so that it will fault if accessed and gives physical 0x11100000 to the current memory allocation request (could be the same application or a different application).
When this application comes around and accesses the memory chunk at 0x20000000, the operating system gets the fault, picks some other chunk of memory, saves it to disk, marks it to fault, takes what was at this applications 0x20000000 pulls it from disk places it in ram, releases the fault and the application keeps going. This is why performance falls off a cliff when you run out of memory in your system and it runs into "swap" memory sometimes also called virtual memory.
If the mmu is there and the processor is designed to be use with operating systems, then ideally there is a fast way to switch the mmu tables. For a single threaded processor to make this simpler only one thing can run at a time even though it feels to the user there are many things going on, only one set of instructions are running at a time and they are either from a specific application or handler within the operating system. Each processor id needs an mmu table each application and the kernel itself (you don't turn off the mmu normally you simply give the kernel full access to the memory space or the mmu knows a specific id is not checked, specific to the design of the mmu/system). The mmu tables live in memory but the mmu does not have to go through itself to get there its not a chicken and egg thing, the operating system simply never allocates that memory to anyone, it protects it. The mmu can either be such that it combines the id and upper section of the virtual address to find the mmu table entry or in a single threaded system there could be one active table and the OS switches which table is used or which id has access to chunks, or let's think of it this way you could have only two ids for a single threaded system. Getting too vague here, you would need to look at specific processors/architectures/implementations to see how that one works, how the processor modes works, what ids are generated from that how the mmu reacts to those, etc.
Another feature here which makes life so much easier for all of us is that this also allows application A to have its program at 0x00000000 and application B have its program at (virtual address) 0x00000000 and application C have its program at 0x00000000 because their physical addresses are all at different places. But we can now compile programs for that operating system so that they operate in the same memory space. Pre-mmu or without an mmu then 1) you are likely unprotected but 2) you can certainly still have an operating system with applications.
You would need to have the operating system move memory around or force position independent code so that when launched each application either starts at a known address but the OS has moved/swapped another application out of the way or position independent and each application starts in a different space. In order to support memory allocation the OS would need to work harder to keep track, and try to have an algorithm that tries to avoid fragmentation, sometimes having to copy data when an application re-allocates.