As @S.Lott said, the answer is "yes". The more complicated answer is, "There are many ways this can be done." Read on for a full explanation of many of the common ways in which memory can be shared across concurrent requests.
To understand more, you need to understand how memory is actually managed by the operating system, and used by the program.
The operating system assigns memory to processes. Processes are running programs that cannot affect each other's memory. Within a process there can be one or more threads that the operating system knows about. Each thread is attempting to do things, access memory, etc, and can wind up (at the operating system's discretion) being scheduled sequentially or in parallel on one or more CPUs. (Schedulers prefer to keep threads on a single CPU, but not always.) Each thread is then responsible for doing whatever it wants to do. A thread can be, depending on the program, doing one thing or multiple things.
You are familiar with a multiple processes model where memory is assigned to processes that then each do one thing. In this model every process has separate memory. Thus if you have 10 processes, each of which has loaded 8 MB of data, you have 80 MB of memory required. In PHP by default each process may serve many requests over its lifetime, but the maximum number of parallel requests being served is the same as the number of processes you have running.
The next option is multi-threading. So you can load data into the thread, and then multiple threads can read that data. Many languages use multi-threading in this form. For instance Java and Python do that. The disadvantage to this style is that weird things can happen when 2 threads are both doing something with the same memory, and that is no fun to debug.
Moving on, the trick that Node.js uses is simpler. Remember that I said that depending on the program a thread can do one or multiple things? Well Node.js uses a callback style of programming that every time you have an event it does something, registers a callback for the next event, then does something else. With this style a single thread can be waiting for events for many requests. This style can be used in any language, but it is more convenient when you have an entire framework that does the same thing. Some languages actually have facilities to hide all of the callback details from you. If you hear about "green threads" or "cooperative multi-threading", that is what is going on. Examples of languages that use this include Ruby and Racket.
The next trick is that the operating system can be explicitly told to share blocks of memory. This is available with low-level calls to things like mmap
, which can be exposed in higher level languages in various ways.
Moving on, the operating system sometimes knows that it can share memory. This is what is happening in the case of compiled languages. Shared libraries can be read from, but not written to. Such read-only memory can be loaded into many processes. You've seen this when shared libraries get loaded into many C programs at once. In principle an interpreted language could use this mechanism itself, and I've seen discussion about doing so, but it generally doesn't make sense and they don't.
And a final trick. Many operating systems (all variants of *nix for example) have the idea of copy-on-write memory. This memory can be shared between any number of processes, but any process that tries to write to it will see the memory copied elsewhere, will be pointed there, and can then read and write from there. This is used to make the fork
call efficient. If you're using, for instance, Apache with a pre-fork model, you can take advantage of this by loading a lot of data into the parent Apache process. Then every forked copy will start off sharing a lot of data. Over time that data comes unshared, but if you kill processes after a certain time you can reduce memory pressure. I don't know how easy this is to do with PHP, but it is a very common mod_perl trick, and can let memory be shared.