Matt Jacobson
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.
WebProcessCache
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
.
-
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. ↩︎