Profiling Memory in Android WebViews
Your hybrid app’s embedded web screens slow down or go blank after a while, crash reports mention the renderer process being killed, and you cannot see the web content’s memory from Android Studio’s profiler. This guide from Remote Debugging Memory on Mobile Browsers, in the Browser DevTools & Performance Profiling Workflows section, shows how to attach Chrome DevTools to a WebView, where WebView memory actually lives on the device, and how to survive renderer terminations.
| Symptom | Root Cause | Immediate Action | Measurable Impact |
|---|---|---|---|
WebView not listed in chrome://inspect |
Web contents debugging disabled in the app | Call WebView.setWebContentsDebuggingEnabled(true) in debug builds |
Full DevTools, including Memory panel, on device |
| Android Studio profiler shows low app memory, yet screens go blank | WebView content runs in a separate renderer process | Measure the renderer with dumpsys meminfo |
Reveals the process that actually grows |
| White screen after returning to the app | Renderer killed under memory pressure | Implement onRenderProcessGone and recreate the WebView |
App recovers instead of showing a blank screen |
| Memory grows each time a web screen opens | Old WebView instances not destroyed | Call destroy() on removed WebViews; avoid holding Activity context |
Renderer memory returns after screen closes |
| JS heap fine, footprint high | Images, canvases and DOM outside the JS heap | Compare snapshot totals with process PSS | Targets the right kind of memory |
Root Cause: The WebView Renderer Is Its Own Process
Android System WebView is built from the Chromium codebase. Since Android 8.0 it runs web content out of process by default: the app process hosts the WebView view and the browser-side logic, while JavaScript, the DOM, layout and decoded images live in a separate, sandboxed renderer process. That split has two consequences for memory work.
First, the app-side tools do not see web memory. Android Studio’s memory profiler attaches to your app’s process and reports its Java/Kotlin and native heaps. The megabytes consumed by a leaking single-page app inside the WebView are in the renderer process, which appears in adb shell dumpsys meminfo as a separate sandboxed_process entry associated with WebView. Teams frequently conclude “memory is fine” from the app profiler while the renderer grows unchecked.
Second, the renderer can be killed independently. When the device runs low on memory, Android’s low-memory killer may terminate the renderer process — especially while the app is in the background. The WebView then has no content; without handling, users see a white screen when they return. Since API level 26 the app is told via WebViewClient.onRenderProcessGone(), which reports whether the renderer crashed or was killed, and must destroy and recreate the affected WebView.
For the JavaScript side, a WebView behaves like any Chromium page once you enable debugging: chrome://inspect on a desktop Chrome, with the device connected over USB as in debugging Android Chrome memory over USB, lists each debuggable WebView and opens full DevTools, including heap snapshots and allocation profiles. The standard leak workflow — the three-snapshot technique — then applies unchanged.
Step-by-Step Fix
- Enable WebView debugging in debug builds. Add
WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG)in yourApplication.onCreate. Verification: with the device connected by USB and USB debugging on,chrome://inspect/#deviceson your desktop lists the app’s WebView pages. - Record the renderer’s memory from the device. Run
adb shell dumpsys meminfoand find the WebView renderer process (listed assandboxed_processwith your app’s WebView), then runadb shell dumpsys meminfo <pid>for its breakdown. Verification: you have the renderer’s total PSS in MB before exercising the web screens. - Exercise the suspected flow ten times. Open and close the web screen, or navigate within the SPA, ten times, then read the renderer PSS again. Verification: growth per repetition is visible (for example +8 MB per open).
- Snapshot the JS heap through DevTools. Click inspect for the WebView, open Memory, and apply the three-snapshot technique around the same flow. Verification: you can tell whether the growth is in the JS heap (snapshot totals grow) or elsewhere (PSS grows, snapshots flat).
- Check WebView lifecycle on the app side. Ensure closed screens call
webView.destroy()after removing it from the view hierarchy, and that no static field or long-lived object keeps aWebViewor itsActivitycontext. Verification: after closing the screen, the renderer’s PSS falls and the Android Studio profiler shows no retainedWebViewinstances. - Handle renderer termination. Override
onRenderProcessGone, remove and destroy the dead WebView, recreate it, and restore state. Verification: killing the renderer withadb shell kill <renderer pid>on a debug build no longer leaves a blank screen.
Command and Code Reference
Use case: enable debugging and recover from renderer termination (Kotlin).
// Application.kt — debuggable WebViews in debug builds only
class App : Application() {
override fun onCreate() {
super.onCreate()
WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG)
}
}
// WebScreenFragment.kt — recover when the renderer is killed for memory
webView.webViewClient = object : WebViewClient() {
override fun onRenderProcessGone(view: WebView, detail: RenderProcessGoneDetail): Boolean {
// detail.didCrash() == false means the system killed it (usually memory)
container.removeView(view)
view.destroy() // release the dead instance
webView = createWebView().also { container.addView(it) }
webView.loadUrl(lastUrl) // restore state from your own storage
return true // we handled it; do not crash the app
}
}
override fun onDestroyView() {
container.removeView(webView)
webView.destroy() // frees the renderer-side page
super.onDestroyView()
}
Use case: sample renderer memory from a shell while testing.
# List processes and find the WebView renderer for your package
adb shell dumpsys meminfo | grep -i -E "sandboxed_process|webview"
# Detailed breakdown for one PID (TOTAL PSS is the number to track)
adb shell dumpsys meminfo 12345 | grep -E "TOTAL|Native Heap|Graphics"
# Sample every 10 s while you run the flow; stop with Ctrl+C
while true; do adb shell dumpsys meminfo 12345 | grep "TOTAL PSS"; sleep 10; done
Verification and Regression Prevention
A fix is complete when the renderer’s PSS returns to a stable plateau after repeated opens, heap snapshots taken through chrome://inspect show no growing constructors, and the Android Studio profiler shows no retained WebView or Activity instances after closing web screens. Test on a low-memory device or emulator image as well; renderer kills that never happen on a flagship phone are routine on 2–3 GB devices.
For regression protection, script the open/close flow with an instrumentation test and log dumpsys meminfo for the renderer before and after ten iterations, failing when growth exceeds a set budget. On the web side, the same automated leak tests you run in desktop Chrome catch most JavaScript leaks before they reach the WebView. Budgets for memory-constrained devices are discussed in setting memory budgets for low-end devices.
Edge Cases and Gotchas
Several WebViews share one renderer
All WebViews in an app typically share a single renderer process. One leaking web screen therefore affects every other embedded page, and a renderer kill takes all of them down at once — onRenderProcessGone is called for each affected WebView, and each must be recreated.
Release builds are not debuggable
chrome://inspect only lists WebViews in apps that enabled content debugging. Reproduce production issues with a debug or internal build that shares the same web bundle, or add a hidden developer setting that enables debugging for internal testers.
JavaScript interfaces retain Java objects
Objects exposed with addJavascriptInterface are referenced from the renderer side for the lifetime of the page. Exposing an Activity or a large manager object keeps it alive until the WebView is destroyed. Expose small, dedicated bridge objects and destroy the WebView when its screen closes.
Background timers keep working
A WebView in a background activity may keep running JavaScript timers unless you call onPause() and pauseTimers() appropriately. Pause them when the screen is hidden to cut both CPU use and allocation that would otherwise build up while nobody is looking.
Frequently Asked Questions
Why can’t Android Studio’s profiler see my web app’s memory?
Because WebView content runs in a separate sandboxed renderer process, while the profiler attaches to your app’s process. Use dumpsys meminfo for the renderer’s totals and Chrome DevTools via chrome://inspect for its JavaScript heap.
What should happen when onRenderProcessGone is called?
Remove the affected WebView from the view hierarchy, call destroy() on it, create a new WebView, and reload content from state you saved yourself. Returning true tells the system you handled it; returning false lets the app crash, which is the default if you do nothing.
Can I use the same leak-detection tests as for Chrome?
Largely, yes. The JavaScript engine and DOM are Chromium’s, so leaks found in desktop Chrome with Puppeteer or Playwright usually reproduce in the WebView. Device-specific issues — memory limits, renderer kills, image decoding costs — still need testing on real hardware.
Related
- Remote Debugging Memory on Mobile Browsers — the parent topic
- Debugging Android Chrome Memory over USB — the connection setup shared with WebViews
- Why iOS Safari Reloads Your Tab Under Memory Pressure — the iOS counterpart to renderer kills
- Browser DevTools & Performance Profiling Workflows — the section overview