So, I got some more time to look at this today and need to change my answer a bit. Let me actually jump back to here:
All the documentation I was able to consult says that macOS is designed to use up to all available storage on the startup disk (which is the one I am using since I have only one disk and the available space aforementioned reflects this) for swapping, when physical RAM is not enough.
Did you actually see this in our documentation and, if so, where? If any of our "modern" documentation actually says that we do this, then that's something I'd like to clean up and correct.
So, let me go back to the idea here:
macOS is designed to use up to all available storage on the startup disk
That's the "classic” UNIX system machine swap and, historically, it's how Mac OS X originally worked. However, the downside of this approach is "swap death" in one of two forms:
-
"Soft", meaning the system has slowed down to the point that the user is now no longer willing to use the machine, even though it technically still "works".
-
"Hard", meaning the system outstanding swap "debt" has become so high that the system can no longer make forward progress, as the time required to manipulate the VM system swamps all other activity.
The distinction I'm drawing here is about recoverability— the "soft" state is recoverable, as the machine is still functional enough that the user can shutdown/terminate work, returning the system to normal. Hard is not recoverable, as the system itself has become so buried in VM activity that it can't resolve the issue.
Historically, the slow performance of spinning disk meant that this issue was largely self-regulating, as the machine would be come slower and slower in a relatively "linear" way and the user would then push the machine as much as they could tolerate.
That basic concept is well understood, but what's NOT commonly understood is how the dynamics of those failures have changed as hardware and software have evolved.
However, two major things have changed over time:
-
Increasing CPU power meant that it became feasible to compress VM without obvious performance impact, allowing us to store and retrieve higher volumes of physical memory.
-
SSDs dramatically improved I/O performance, particularly random I/O, allowing the system to "jump around" on physical media in a way it really can't on spinning disks.
Those both dramatically increase the benefit of VM, but they also create new scenarios. Notably:
-
SSDs are fundamentally "consumable" devices, which eventually run out of write cycles. Allowing unbounded swap file usage to destroy hardware is obviously not acceptable.
-
Compression can slow/delay freeing memory/swap, since freeing memory can require additional memory as the system has to decompress swap so that it can dispose of the freed memory, then recompress the data it still needs.
-
The combination of compression and SSD performance makes it possible for the machine to swing VERY suddenly from operating normally into swap death with very little notice or warning.
Expanding on that last point, sequences like this become possible:
-
The user uses a high memory application, which builds up significant memory use.
-
The user backgrounds that application and moves on to other work for an extended period of time. As a result, "all" of that application’s memory is compressed and streamed out to disk.
-
The user switches back to the app and immediately starts trying to interact with "all" of its memory.
Under very high load, that sudden usage swing can actually overload the entire machine, as it's trying to simultaneously "flip" the entire state of the system. Critically, the difference here isn't that it can't happen on a spinning disk (it can), it's that the slower performance of a spinning disk meant that it was far less likely to happen "suddenly".
So, let's go back to here:
however, once about 40 or so swap files are created, for a total of about 40GB of virtual memory occupied
What's actually going on here is that the system has artificially capped the amount of swap space it's willing to use. The size is derived from total RAM (more RAM, higher limit), and I suspect you have a 16 GB machine, as I hit exactly the same limit on my machine. However, the limit isn't tied to the specific size, but it is actually tied to the amount of swap being used. In my test app, I hit the limit at ~45 GB when using rand() to fill memory, but was able to go to ~88 GB when I filled with "1s". Better compression meant more memory.
That then leads back to here:
Is there a way to keep the process from being killed?
No, not when working with VM-allocated memory. The system’s swap file limits are basically fixed, based on total RAM.
However, if you really want to exceed this limitation, the most direct approach would be to allocate 1 or more files, mmap those files into memory, then use that mapped memory instead of system-allocated memory. That bypasses the limit entirely, since the limit is specifically about the system swap file usage, not memory usage. However, be aware that this approach does have the same NAND lifetime issues.
__
Kevin Elliott
DTS Engineer, CoreOS/Hardware