Though it adds complexity, virtual memory is a necessary part of any general-purpose system since it provides reliability through process isolation while maximizing efficiency through shared mappings and paging.
While protection bits on memory pages are sufficient to prevent errant applications from writing outside their assigned memory, virtual memory's process isolation model is especially elegant. With virtual memory, each process has its own virtual address space. This illusion is accomplished through tables which map virtual addresses to physical addresses. Implementing isolation is easy; one process' mappings simply don't exist is another process' tables. Since all memory accesses are through virtual addresses, the physical addresses of the other process cannot be touched.
While providing fault-tolerance, virtual memory also enables us to violate process isolation to safely provide shared memory and improve performance. For example, shared memory can be implemented by sharing the mappings between tables. This is especially useful for sharing code which in many systems is immutable while running. Another example is copy-on-write. Many processes copy data to move it from one address space to another without modifying the data afterwards. Instead of copying data between processes, the operating system can create a shared, protected mapping. If one process tries to modify that memory, the copy is actually performed. In this way, many copies can be defered or avoided. Thus, through the use of shared mappings, virtual memory enables us to reduce both memory and processor requirements.
Combining virtual memory with paging allows us to make maximal use of relatively scarce main memory by transparently moving unused or less-used code and data to disk. For example, let's take the scenario with two processes A and B and a machine without enough main memory for both. Process A is a large spreadsheet but only a small portion of it is being currently used. Process B is a daemon, whose memory requires are large but transient. With paging, we can swap out the currently unused portion of A, allowing B to run without hitting A too hard. Note the analogy to timesharing; in both cases, the principal resource can be yielded to acheive optimal use.
More importantly, virtual memory with paging allows the user to make a trade-off between performance and cost while preserving reliability. Without paging, the user's only option is to purchase more RAM. With paging, the user can choose to take the performance hit of using disk as memory. While some may argue that the same tradeoff can be realized with overlays or loadable modules, these features have to be explicitly coded into the program. This has two disadvantages. First, not every developer has the resources or the will to build software like this. Second, we add the complexity of swapping code to each application, creating multiple points of failure and arguably reducing reliability. In contrast, paging with virtual memory provides the benefits of overlays transparently across all applications with one point of failure - the operating system.
In summary, virtual memory provides elegance, reliability, and flexibility as well as potentially reduces resource requirements. These features outweigh its complexity and make it an integral part of any general-purpose system.