There are large projects that handle OOM and it isn't that hard to do.
Imagine every function in your call stack handles errors consistently. In most cases these functions will bubble up all errors to the caller. In many cases they will perform allocations themselves and free those allocations when they fall out of scope due to either success or error.
The function at the top of the stack hits a malloc error. It will bubble up its status to its caller, who will do the same for his caller, etc. The chain of functions will free intermediate allocations they made along the way. By the time you get to some top-level or near-top-level function, you can react to the error, and you likely even have quite a bit of heap space when the rest of the stack frees its work. But if you don't happen to have the heap space, then you can structure that top-level error handler so that it performs very few allocations, does allocations at upfront at initialization time, etc.
I don't think any of this is hard and I've seen it work well in practice. It's sad to me when I see the opposite, some unreasonable allocation quite reasonably fails, and it takes down the entire process because whoever wrote that code thought it was too hard to do otherwise.