Disabling WebKit's process caches

If (like me) you're running an older Mac, you might have noticed by how much of a memory hog the web is these days. One thing that makes the problem even worse is WebKit's default behavior when you close a tab. Instead of terminating the WebProcess promptly, it leaves it alive for a while, as a sort of optimization to avoid relaunch latency.

There are a couple problems with this. First, I'm much more willing to pay the relaunch penalty than I am to carry around a process holding onto hundreds of megs of memory. Second, mitigations for speculative-execution sidechannel attacks means that WebProcess reuse is more difficult than previously.

In theory, WebKit clears these caches on memory pressure, but for complicated reasons that doesn't work very well in practice.

So I've begun using the following user defaults values to slim down WebKit's memory usage:

$ defaults write -g WebProcessCacheCachedProcessLifetimeInSeconds -float 1.0
$ defaults write com.apple.Safari WebKitPreferences.usesPageCache -bool NO

These defaults control two different WebKit caches that affect process lifetimes.


The first is WebKit's WebProcessCache. A process is added to the cache when its page[1] is closed, and it's eligible for reuse when Safari requests a new process for the same domain. By default, an unused cached process dies on its own after 30 minutes, and the entire cache is cleared if Safari resigns active for more than five minutes. (As if, these days.)

WebProcessCacheCachedProcessLifetimeInSeconds overrides that 30-minute timer. (The latter timer can also be overridden by setting WebProcessCacheClearingDelayAfterApplicationResignsActiveInSeconds.) Conceptually, I'd like to disable the cache entirely, but the logic there rejects overrides of less than a second, so that's what I'm left with.

WebKit logs this out on launch when it notices the override:

Safari: (JavaScriptCore) Warning: WebProcessCache cachedProcessLifetime was overriden via user defaults and is now 1 seconds

Back–forward cache

The second is WebKit's "back–forward cache". When a tab navigates to a new page, the existing page is kept around as a "suspended page" in the "back–forward cache". If the old and new pages are from the same domain, the same WebProcess hosts the new page. But if not, a new WebProcess is spawned (or, if possible, reused from theWebProcessCache) to handle it.

In the latter case, the existing WebProcess is kept alive just to hold onto the suspended page. Since suspended pages are kept alive for up to 30 minutes or until evicted due to capacity cap (by default, two pages, depending apparently on some [very outdated] main memory size checks), that means that the existing process is kept around that long, too. During that time, it's not eligible for reuse. And once the suspended page is evicted from the back–forward cache, its WebProcess is still eligible to be placed in the WebProcessCache.

Use of the back–forward cache is controllable by WebKit private API. On launch, Safari sets this property with the value read from the user default WebKitPreferences.usesPageCache.

  1. In some circumstances, a single WebProcess can manage multiple tabs. IIRC, there used to be a limit of 20 WebProcesses total, but that limit was removed a few years back. ↩︎