WebView and memory

WebView is a powerful component that allows you to display web content within your Android application. However, because it's essentially a full-featured browser engine (Chromium), it has a significant memory footprint and a complex multi-process architecture.

Technical background: Multi-process architecture

On modern Android-powered devices, WebView uses a multi-process model to improve security and stability. When your app uses a WebView, the memory is distributed across different processes:

  1. Browser process (the app process): This is your application's main process. It contains the Java WebView object and the "browser" part of the Chromium engine. This process manages the UI, network requests, and GPU rendering (integrated directly with the Android HWUI rendering pipeline). Unlike Chrome, WebView does not have a separate GPU process.
  2. Renderer process: This process is responsible for parsing HTML, executing JavaScript, and layout. It is isolated from the rest of the system for security. Currently, apps just get one renderer process for all WebViews (except for a few rare special cases), unlike Chrome which often uses separate renderer processes for different sites.

WebView Architecture

Why this matters for memory

When you use dumpsys meminfo <your_package>, you only see the memory used by the browser process (your app process). The memory used by the renderer process is accounted for separately.

Inside the browser process, WebView memory is distributed as:

  • Java heap: Contains the WebView Java wrapper and related objects.
  • Native heap: Contains the Chromium browser engine's internal data structures, caches, and state. Note that due to the use of PartitionAlloc, some WebView native allocations might not be counted under "Native Heap" in dumpsys meminfo and may instead appear under "Other" or "Unknown".
  • Shared memory: Used for sharing graphical buffers and other data. This may not be clearly categorized by dumpsys meminfo.

Troubleshooting tools

Chrome DevTools

The most powerful tool for analyzing the memory inside the WebView (the renderer process) is Chrome DevTools.

  1. Enable WebView debugging in your app:

    // NOTE: In production, this should be gated behind a developer setting
    // or only enabled for debuggable builds to prevent reverse engineering.
    WebView.setWebContentsDebuggingEnabled(true);
    
  2. Connect your device via USB.

  3. Open Chrome on your host machine and navigate to chrome://inspect/#devices.

  4. Find your app and click inspect.

  5. In the DevTools window, go to the Memory tab to take heap snapshots or record allocation timelines for the JavaScript heap.

dumpsys meminfo

Use adb shell dumpsys meminfo --all <package> to see a breakdown of memory. Look for the WebView category in the output and the object counts.

Profiling the renderer

Since the Renderer runs in a separate process, you can't profile its native heap by just profiling your app. You must identify the PID of the renderer process specifically.

To identify the correct renderer PID when multiple WebViews are active:

  1. Use dumpsys activity:

    adb shell dumpsys activity processes <your_package_name>
    

    Look for the mConnections section. You will see a ConnectionRecord linking your app to a SandboxedProcessService. The PID of that process is your renderer. Example:

    mConnections:
      - ConnectionRecord{... com.android.memorylab/org.chromium.content.app.SandboxedProcessService0:0 ...}
    
  2. Check Process Names: Renderer processes are usually named com.google.android.webview:sandboxed_processX or similar. If only one app is using a WebView, there will likely be only one.

Once you have the PID, you can profile it using heapprofd.

Best practices for WebView memory

Explicit destruction

Apps are expected to call WebView.destroy() to indicate when they're finished with an instance.

While WebView tries to ensure that instances can be garbage collected and release all their resources automatically, this is hard to guarantee in 100% of cases. Even when automatic garbage collection works, it may be significantly delayed, causing the app to hold onto resources much longer than expected.

If an app calls WebView.destroy() at the appropriate time (e.g., in Activity.onDestroy()), holding onto a reference to the WebView object itself will not leak any significant native resources. There is no strict need to null out references to the WebView object in Activity fields after destroying it, as it will be cleaned up when the Activity itself is garbage collected.

Exercises: hands-on with WebView memory

Exercise 1: observing the multi-process footprint

  1. Launch MemoryLab and take a baseline measurement of your app's memory:

    adb shell dumpsys meminfo com.android.memorylab
    

    Sample baseline (rango): TOTAL PSS: 18915 KB

  2. Tap Launch WebView (Normal).

  3. In the WebView, tap Allocate JS Memory (1000 DIVs) several times.

  4. Check the app's memory again:

    adb shell dumpsys meminfo com.android.memorylab
    
  5. Observe that the memory in your app process doesn't increase significantly compared to the baseline! This is because the DOM elements are in the Renderer Process.

  6. Find the renderer process:

    adb shell ps -A | grep webview | grep sandboxed
    

    Example output:

    u0_i9002     14227  1087    1632732 135880 do_epoll_wait       0 S com.google.android.webview:sandboxed_process0
    
  7. Check the memory of the renderer process (using its PID):

    adb shell dumpsys meminfo 14227
    
  8. Observe the high TOTAL PSS of the renderer process. In our sample run, it jumped to ~55MB after a few allocations. Note that JavaScript allocations (handled by the V8 engine) typically contribute to the Private Other or Unknown (mmap) sections of dumpsys meminfo, rather than the Dalvik Heap.

Exercise 2: the Java-side WebView leak

A common mistake is holding onto a WebView instance in a static field or in a long-lived object that leaks. Because the WebView object is a heavy "anchor" that holds onto native resources and potentially entire renderer processes, leaking it is very costly.

WebView Leak Impact

  1. In MemoryLab, tap Launch WebView (Java Leak).
  2. The activity will automatically close after the page is loaded (simulating repeated navigation and leak accumulation).
  3. Tap the button 4 times.
  4. Check the number of WebView instances in your app:

    adb shell dumpsys meminfo com.android.memorylab
    

    Look for the Objects section at the bottom. You will see the count for WebViews has increased to 4.

    Sample output (4 leaked instances) on rango:

     Objects
               Views:       51         ViewRootImpl:        5
         AppContexts:       14           Activities:        5
              Assets:       38        AssetManagers:        0
       Local Binders:       55        Proxy Binders:       77
       Parcel memory:       41         Parcel count:       68
    Death Recipients:        3             WebViews:        4
    
  5. Capture a heap dump and use AHAT to find the leak. If you don't have ahat in your path, you can build it from the Android tree:

    # Dump heap from device
    adb shell am dumpheap com.android.memorylab /data/local/tmp/heap.hprof
    adb pull /data/local/tmp/heap.hprof
    # Run ahat using the built JAR (found in out/host/linux-x86/framework/)
    java -jar out/host/linux-x86/framework/ahat.jar -p 8888 heap.hprof
    
  6. In the AHAT web interface (localhost:8888), click on the allocations link (or sites) in the top menu to see the overall memory usage.

    AHAT Allocations

  7. Search for the android.webkit.WebView class. Click on its instance count to see all live instances. You should see multiple instances in the list.

    AHAT WebView Instances

  8. Click on one of the leaked WebView instances. Scroll down to the Sample Path from GC Root section. You will see it is being held by the sLeakedWebViews list in com.android.memorylab.WebViewActivity.

    AHAT Path to GC Root


← Native | ↑ Up | App code →