However, with operating systems and multiple programs running at the same time, memory is no longer contiguous: instead, programs can request "pages" (blocks of memory). This is (more or less) what `malloc` does, if you've come across it. That's the key difference: in a modern operating system, you can't expect memory to be one big array, since your program might have requested more than one page of memory. In that sense, it's more like a collection of smaller arrays.
We have to do it this way so we can have memory protection (similar to file permissions - a program can decide if other programs can read one of their pages, write to it, etc) and swapping (i.e writing unused pages to non-volatile storage , like a hard drive, to free memory).
(All of this is to say nothing of NUMA.)
However, one of the responsibilities of the OS is to hide all that messy detail from the bare-metal programmer or compiler writer and provide a simple(r) abstraction over the hardware. Thus, "(physical) memory is a big array".
In essence the situation is pretty much same as for user space program: you get big address space and list of memory regions that are mapped and usable.
http://www.tldp.org/LDP/tlk/mm/memory.html
http://www.cs.umd.edu/class/sum2003/cmsc311/Notes/Memory/vir...
Very simple:
Registers.
L1 cache.
L2 cache.
NUMA
Somewhat more complex:
Virtual memory.
Memory pages.
Other posts have more information, but that should get you going.