bloginnerellipses
Blog
  • performance

Deep Dive: RAM Internals in WebKit

January 5, 2026

Deep Dive: RAM Internals in WebKit

If your users are experiencing page crashes on iOS - particularly on older iPhones - you likely have a memory problem. And unlike CPU performance issues, memory problems in WebKit are poorly understood, difficult to diagnose, and can create cascading failures that make things exponentially worse.

This guide explains how WebKit manages memory on iOS, why pages crash, and what you can do about it.

Why RAM Matters for Web Performance

Most web performance discussions focus on CPU - how long scripts take to execute, main thread blocking, interaction latency. But on mobile devices, particularly iOS, RAM is often the silent killer.

When a page exceeds available memory, it doesn't slow down gracefully. It crashes. The tab reloads. If it happens repeatedly, the tab gets marked as crashed. Users see a blank page or an error message. They leave.

This is especially problematic for content-heavy sites with significant JavaScript, ad tech, and third-party integrations. The combination of large bundles, hydration overhead, and dynamic ad content can push memory usage past iOS limits surprisingly quickly.

iOS Heap Size Limitations

Here's the part that catches most developers off guard: iOS devices have far less available heap memory than you'd expect.

Device Approximate Heap Limit
iPhone 6s / SE (1st gen) ~200-250MB
iPhone 8 / X ~300-350MB
iPhone 11 / 12 ~350-400MB
iPhone 13 / 14 ~400-450MB
iPhone 15+ ~1GB+

These numbers vary based on system load, background apps, and iOS version. The newer devices have significantly more headroom, but a huge portion of your mobile traffic is likely still on older devices with much tighter limits.

For comparison, your development MacBook probably has 16-32GB of RAM. Chrome on desktop can happily consume gigabytes. When you're testing locally, memory issues are invisible.

This is why pages that work perfectly in development crash repeatedly for real users on mobile Safari.

How WebKit Manages Memory

On iOS, two memory management systems work together (and sometimes against each other):

WebKit MemoryPressureHandler - WebKit's internal memory management system. The source code is available in the WebKit repository, so we can see exactly how it works.

iOS Jetsam subsystem - Apple's system-wide memory management. Applies to all apps, including Safari. More aggressive than WebKit's own handling. Source code is not publicly available.

The critical point: the lower of the two limits always applies. If Jetsam decides your page is using too much memory, it can force WebKit to act - or kill Safari entirely.

The base threshold is calculated as:

baseThreshold = min(3 GB, min(physical_RAM, jetsam_limit))

(Source: AvailableMemory.cpp)

In practice, for web pages on iOS, this typically lands in the 300-450MB range for most devices currently in use.

WebKit Memory Pressure Events

WebKit defines three memory pressure levels, each triggering increasingly aggressive cleanup:

Conservative Mode (50% of limit)

Triggered when memory usage exceeds half the available limit. WebKit checks this every 30 seconds, or immediately if Jetsam sends an event.

What gets cleared:

  • Render caches (layout line caches, text breaking caches)

  • Dead resources (unused cached images, CSS, JS)

  • Glyph caches (font rendering caches)

  • Triggers garbage collection (non-blocking)

    case MemoryUsagePolicyConservative:
    releaseMemory(Critical
    No, Synchronous::No);

(Source: WTF/wtf/MemoryPressureHandler.cpp)

This is relatively gentle - pruning caches that can be rebuilt if needed.

Strict Mode (65% of limit)

Triggered at 65% of the limit. This is where things get serious.

What gets cleared:

  • Back/forward cache (destroys cached pages)

  • Decoded image data (keeps compressed data only)

  • CSS value pools

  • Audio HRTF cache (Web Audio)

  • Cookie cache

  • Query selector results

  • Font caches (emptied entirely)

  • Media buffers (video/audio buffered data)

  • All JIT-compiled JavaScript code

    case MemoryUsagePolicyStrict:
    releaseMemory(Critical
    Yes, Synchronous::No);

(Source: WTF/wtf/MemoryPressureHandler.cpp)

That last one is critical. WebKit deletes all the compiled JavaScript, meaning it will need to be recompiled when next executed. For sites with large JavaScript bundles, this creates significant performance degradation as the JIT compiler has to redo all its work.

Kill Threshold (100% of limit)

Same cleanup as Strict mode, but runs synchronously - blocking everything until complete.

  • Immediately deletes all JavaScript code
  • Immediately runs garbage collection
  • Releases FastMalloc memory across all threads (including workers)
  • Waits for cleanup to complete (typically ~500ms)

After cleanup, memory is measured again:

  • If under the limit: process continues
  • If still over: the kill callback executes and the page reloads

The relevant WebKit source code:

void MemoryPressureHandler::shrinkOrDie(size_t killThreshold)
{
    RELEASE_LOG(MemoryPressure, "Process is above the memory kill threshold. Trying to shrink down.");
    releaseMemory(Critical::Yes, Synchronous::Yes);

    size_t footprint = memoryFootprint();
    RELEASE_LOG(MemoryPressure, "New memory footprint: %zu MB", footprint / MB);

    if (footprint < killThreshold) {
        RELEASE_LOG(MemoryPressure, "Shrank below memory kill threshold. Process gets to live.");
        return;
    }

    WTFLogAlways("Unable to shrink memory footprint (%zu MB) below kill threshold (%zu MB). Killed\n",
                 footprint / MB, killThreshold / MB);
    m_memoryKillCallback();
}
Get the free Service Guide

Download our service guide to understand how we can help you optimise your site speed

(Source: WTF/wtf/MemoryPressureHandler.cpp - note that WTF stands for "Web Template Framework")

iOS Jetsam Subsystem

Jetsam is iOS's system-wide memory pressure handler. It monitors all running processes and can send events to WebKit or kill apps directly.

When Jetsam sends a memory pressure event, WebKit responds based on severity:

dispatch_source_set_event_handler(memoryPressureEventSource().get(), ^{
    auto status = dispatch_source_get_data(memoryPressureEventSource().get());
    switch (status) {
        // VM pressure events
        case DISPATCH_MEMORYPRESSURE_WARN:
            respondToMemoryPressure(Critical::No);
            break;
        case DISPATCH_MEMORYPRESSURE_CRITICAL:
            respondToMemoryPressure(Critical::Yes);
            break;

        // Process memory limit events
        case DISPATCH_MEMORYPRESSURE_PROC_LIMIT_WARN:
            didExceedProcessMemoryLimit(ProcessMemoryLimit::Warning);
            respondToMemoryPressure(Critical::No);
            break;
        case DISPATCH_MEMORYPRESSURE_PROC_LIMIT_CRITICAL:
            didExceedProcessMemoryLimit(ProcessMemoryLimit::Critical);
            respondToMemoryPressure(Critical::Yes);
            break;
    }
});

(Source: MemoryPressureHandlerCocoa.mm)

The source code for Jetsam itself is not publicly available, but we know it can be more aggressive than WebKit's 30-second check interval. In extreme cases, Jetsam will kill Safari entirely if memory pressure is too high and WebKit isn't responding fast enough.

Common Culprits for High Memory Usage

Based on our experience auditing sites with iOS crash problems, these are the most common contributors:

JavaScript frameworks - Modern JavaScript frameworks ship significant amounts of code that needs to be parsed, compiled, and held in memory. The bigger the bundle, the more memory pressure you create.

Third-party scripts - Ad tech, analytics, comment systems, social widgets, verification vendors, and other third-party integrations all consume memory. The cumulative impact of multiple vendors can be substantial, and memory usage often grows over time as dynamic content loads.

Diagnosing Memory Issues

Here's the challenge: iOS provides very little in the way of memory diagnostics. Safari's memory profiler is notoriously unreliable on sites with high memory usage - ironically, it tends to break on exactly the sites you need to diagnose.

Using Chrome as a Proxy

Chrome's memory profiler is significantly more reliable. While it won't match Safari's behaviour exactly, it can help you understand your codebase's memory characteristics and identify obvious problems.

To profile in Chrome:

  1. Open DevTools (Cmd+Option+I)
  2. Go to the Memory tab
  3. Take heap snapshots at intervals while using the page
  4. Compare snapshots to identify what's growing

Caveats:

  • Chrome's memory management differs from WebKit's
  • V8 (Chrome's JS engine) handles JIT differently than JavaScriptCore (Safari's)
  • Results should be treated as directional, not absolute

A/B Testing with Blocked Scripts

To identify which scripts are causing problems, test the page with different scripts blocked and compare memory usage. This helps identify whether the problem is your own code or third-party integrations.

Important: diagnostic runs vary substantially between runs due to differences in ad creative, user-generated content, and other dynamic elements. Focus on overall trends rather than absolute values.

Mitigation Strategies

Aggressive Cleanup of Off-Screen Content

For long pages with dynamic content, destroy off-screen content rather than just hiding it:

  • Infinite scroll implementations - As users scroll down and new content loads, remove DOM nodes that have scrolled far out of view. Many infinite scroll implementations just keep appending content, causing memory to grow unbounded.
  • Virtualized tables and lists - For data-heavy applications with large tables or lists, use virtualization techniques that only render the visible rows plus a small buffer. Libraries exist for this, but the key principle is maintaining a constant DOM size regardless of data volume.

The goal is to keep memory usage roughly constant over time, rather than growing linearly with user engagement.

Bundle Size Reduction

Every kilobyte of JavaScript that doesn't ship is memory that doesn't get used. Focus on:

  • Code splitting and dynamic imports
  • Tree shaking unused code
  • Removing unused dependencies
  • Evaluating framework overhead

Remember: the JIT death spiral means that bundle size has an outsized impact on iOS memory stability. A 20% reduction in JavaScript might result in a 50%+ reduction in crashes.

Lazy Loading and Code Splitting

Don't load everything upfront:

  • Split code by route/page
  • Defer non-critical functionality
  • Load below-the-fold content on demand

But be careful: lazy loading that doesn't clean up after itself can make memory problems worse over time.

When SSR Makes More Sense Than SPA

For sites with significant mobile traffic, a server-rendered approach often makes more sense than a single-page application:

  • Far less JavaScript shipped to the client
  • No hydration overhead
  • Memory usage is more predictable
  • Easier to keep within iOS limits

This applies to content-focused sites but also to any application where mobile represents a significant portion of traffic. E-commerce sites, dashboards accessed on mobile, internal tools used on tablets - if users are hitting your app on iOS devices, the architectural choice has real consequences.

This is a significant architectural decision, but if you're fighting constant iOS crashes on a site running a heavy JavaScript framework, it may be worth considering. The engineering effort to optimise a large SPA for iOS memory limits can exceed the effort of rebuilding with a simpler architecture.

Key Takeaways

  1. iOS heap limits are lower than you think - Older devices (iPhone 8/X era) have 300-400MB limits. Even iPhone 13/14 tops out around 400-450MB. Only iPhone 15+ has 1GB+ headroom, but many of your users aren't on the latest devices.
  2. WebKit has three memory pressure levels - Conservative (50%), Strict (65%), and Kill (100%). Strict mode deletes JIT-compiled JavaScript.
  3. Diagnosis is difficult - iOS provides poor memory diagnostics. Use Chrome as a proxy and focus on trends rather than absolute values.
  4. The biggest wins come from shipping less JavaScript - Framework overhead and third-party scripts are the usual culprits.
  5. Clean up off-screen content - For long pages with dynamic content, destroy what's no longer visible rather than just hiding it.

Experiencing iOS crashes on your site? We specialise in diagnosing and fixing web performance issues.Get in touch- we'd love to help.

newsellipse
back

Ready to optimise
your site speed?

Download our service guide to understand how
we can help you optimise your site speed

Download Service Guide