A phone keeps recently used apps alive in the background so returning to one is instant rather than a full restart, and to keep more of them it pushes cold memory out to flash — but every app also runs a garbage collector that periodically walks its entire heap to prove what is still alive, dragging back exactly the pages that were correctly evicted; this paper (Wuhan, CityU and collaborators) confines the collector to the objects allocated since the app went into the background, and separately teaches the phone which objects the next return will actually need.
The argument in one glance
-
Problem
Users switch apps constantly and notice every delay
Returning to an app that is still resident in memory is dramatically faster than starting it from nothing, because none of the initialisation has to happen again. Research on human perception puts the threshold where a delay stops being invisible at around 150 milliseconds, and measurements here show apps that take several hundred milliseconds to resume. So the number of apps a phone can keep alive is a direct determinant of how the device feels to use.
-
What people did
Keep more apps alive by pushing their cold memory to flash
Vendors enable swap, so memory that has not been touched recently is written out to storage and the space is reused. An app can then remain cached while occupying much less physical memory than it nominally holds, which lets more of them coexist. This is standard practice and it does increase the number of cached apps.
-
Why it fell short
Two memory managers, each undoing the other
Every app also runs a tracing garbage collector, which periodically walks its whole object graph to work out what is still reachable. The eviction policy sees those traversals as ordinary memory accesses and pulls the pages back, so a background collection can undo an app's entire eviction and spike memory pressure enough to get cached apps killed. And eviction by least-recent use is the wrong policy for resumption, which needs data nobody has touched in minutes.
-
This paper's idea
Tell the collector where not to look, and the evictor what will be needed
This paper (Huang, Zhang, Qiu, Liang, Ausavarungnirun, Li and Xue) observes that objects allocated while an app was in the foreground behave completely differently from those allocated after it went to the background, and confines background collection to the latter. Separately, it identifies which objects a resumption will actually touch — a surprisingly small and predictable set — and has the runtime tell the kernel to keep exactly those.
Why this problem is worth solving
The quantity being optimised is unusual and worth stating plainly.
WHEN
a user leaves an app, a phone does not necessarily destroy it. If memory allows, the process
stays alive in the background, and returning to it is a HOT LAUNCH — the app is simply brought
back to the front, with no process creation, no class loading, no initialisation. The
alternative is a COLD LAUNCH, which does all of that again and takes several times as long.
The measurements here show hot launches in the low hundreds of milliseconds against cold
launches often exceeding a second, and studies of human-computer interaction put the point at
which a delay becomes perceptible at around 150 milliseconds. So the design question is not
how to make launching faster in general but how many apps can be kept cached — every app that
falls out of the cache converts a fast return into a slow one. That reframes an
interactivity problem into a memory-capacity problem, which is why swap enters the
picture.
The tension comes from two memory managers that cannot see each other.
A
TRACING garbage collector determines what is still alive by starting from a set of roots and
following every reference, visiting the entire live object graph. Those visits are memory
accesses, but they are not accesses the program wanted — they exist to prove reachability. The
eviction policy underneath has no way to tell the two apart. It sees a page being read and
concludes the page is in use, so a background collection systematically resurrects the pages
the evictor had correctly written out. The paper measures the consequence directly: an app
sitting quietly in the background shows almost no accesses until a collection fires, at which
point access counts spike, memory pressure rises, and cached apps get terminated. The second
half of the mismatch is about time. Eviction assumes the recent past predicts the near future,
which is fine while an app idles and wrong at the moment a user returns to it, because
resumption touches a great deal of state nobody has looked at for minutes.
What people did before, and where it broke
-
Enable swap and let the two mechanisms coexist. The shipping configuration on modern phones. Cold pages go to flash, more apps stay cached, and the collector runs as it always has.
-
The gap. Each background collection undoes the eviction, so caching capacity is far below what the swap should provide, and resumption stalls on pages the evictor removed because nothing had touched them recently.
-
Shrink the heap so apps occupy less. Recent work reduces the memory each cached app holds, which straightforwardly allows more of them.
-
The gap. The paper’s central finding is that this trades against resumption speed — the methods that increase the number of cached apps make returning to them slower, which is the thing caching was for.
-
Co-design the collector and the evictor. The closest prior system records each object’s references before evicting it, so the collector can reason about a swapped-out object without touching it. This addresses the resurrection problem directly.
-
The gap. It works at object rather than page granularity, which suits this heap badly since most objects are far smaller than a page; maintaining the reference records demands long pauses where the whole app stops; and it does nothing about which objects resumption needs. It also proved unstable, crashing after roughly four commercial apps.
-
Assume flash is fast enough to fall back on. If eviction guesses wrong, the page is simply read back.
-
The gap. The measurements make this untenable. Main memory reads at about 9.2 gigabytes per second on the test device against 20.3 megabytes per second from the swap partition — a factor of roughly 452, so a wrong guess is not a small cost.
How the two managers are taught about each other
An app's background lifetime under a fore/background-aware design
-
-
new
-
new
-
new
-
new
-
new
The idea
Objects born in the foreground and in the background are different populations. The enabling measurement is a lifetime study. Objects allocated while an app was in the foreground turn out to live much longer and to occupy the large majority of the heap; objects allocated after the app went to the background are short-lived and comparatively few. That asymmetry licenses a strong move — while an app is backgrounded, run collection over the background objects only. Almost all the garbage being generated is there, so almost all the reclamation is still available, while the foreground objects, which are the ones whose pages have been evicted, are never touched and never resurrected. Making this correct requires knowing about references from foreground objects into background ones, which the collector would normally discover by traversal; the implementation instead records them with a second card table and a modified write barrier, exploiting the fact that background objects are all allocated after foreground ones and can be placed in separate regions. Forbidding the collector to enter a region and reconstructing what it would have learned by other means is exactly the manoeuvre of the second-heap work covered elsewhere on this site, arrived at from a different direction.
Resumption touches a small, predictable set of objects. The second observation is the more surprising one. Watching what a hot launch actually accesses, the authors find that two easily identified classes dominate. NEAR-ROOT objects — those within a small distance of the garbage collector’s roots in the reference graph, with a default of two hops — account for about half of the re-accesses. FOREGROUND YOUNG objects, meaning those allocated shortly before the app was backgrounded, account for about forty percent. Together they cover 68 percent of everything a resumption touches while occupying only 15.7 percent of the heap. That ratio is what makes the whole approach work: keeping a sixth of the heap resident buys two thirds of the accesses, so the system can be aggressive about evicting the rest. Finding a cheap signal that predicts what will be wanted, instead of inferring it from recency, is the same shift as the transparent memory offloading work covered elsewhere on this site, which replaced guesswork about coldness with a measured indicator.
One collection pass, repurposed to classify. The elegant part of the implementation is that identifying these classes costs almost nothing extra, because a collection is already traversing the graph. Two changes make it a classifier. The traversal is switched from depth-first to breadth-first with a first-in-first-out queue and depth delimiters, so the distance from the roots is known as each object is reached and near-root objects fall out for free. And the read barrier that the concurrent collector already runs in application threads is used as a second signal: an object marked by an application thread rather than by the collector was genuinely being used, so it is classified as working set and protected. The result is three page categories — launch, working set, and cold — with objects grouped into pages by category, which also solves a mismatch the paper measures separately, since most objects are far smaller than a page and a page holding a mixture cannot be classified at all. That the collector’s own traversal is a rich source of information usually discarded is a theme of the runtime measurement work covered elsewhere on this site.
Turning a collection into a classification
How one garbage collection sorts the heap into three kinds of page
The classifying pass that runs ten seconds after an app moves to the background.
Step 1 of 5
Traverse breadth-first instead of depth-first
The collector normally follows references depth-first. This pass uses a first-in-first-out queue with delimiters marking each level, so every object's distance from the roots is known when it is reached.
Depth-first traversal visits the same objects and gives no depth information without extra bookkeeping. Breadth-first costs nothing more and makes near-root objects fall out of the traversal order itself, which is why the change is free.
Why it works
- The two object populations have genuinely different lifetimes. Foreground objects live much longer and occupy most of the heap while background objects are short-lived → tracing only the latter still reclaims nearly all the garbage → the collector’s working set drops from about 700,000 objects to around 100,000, a factor of seven.
- Untouched pages stay evicted. Confining traversal keeps the collector away from the pages the evictor removed → they are not resurrected and memory pressure does not spike → more apps survive in the cache, which was the point of enabling swap.
- A small set covers most of what resumption needs. Near-root and recently-allocated foreground objects together account for 68 percent of re-accesses on 15.7 percent of the heap → keeping a sixth resident buys two thirds of the accesses → everything else can be evicted aggressively without hurting the return.
- The classification rides on work already being done. Switching the traversal to breadth-first yields depth for free and the existing read barrier yields the working set → no separate profiling pass is required → the cost of knowing which objects matter is close to zero.
- Grouping makes pages classifiable at all. Placing objects of one category together → each page has a single answer to whether it should be evicted → the mismatch between an object-sized heap and a page-sized eviction unit stops mattering.
The evaluation
| Property | What was measured | The result | What it tells you |
|---|---|---|---|
| How fast apps resume | |||
| Whether the improvement is perceptible | |||
| How many apps stay cached | |||
| Why the caching improves | |||
| Cost to the app in the foreground |
Check yourself
Check your understanding
1. Why does running a garbage collector on a backgrounded app reduce how many apps a phone can keep cached?
2. What justifies restricting background collection to objects allocated after the app was backgrounded?
3. How does the system predict which objects a resumption will need, and why is that better than recency?
4. Predict what goes wrong if objects are classified but not grouped into pages by category.
Sources
More Apps, Faster Hot-Launch on Mobile Devices via Fore/Background-aware GC-Swap Co-design. Jiacheng Huang, Yunmo Zhang, Junqiao Qiu, Yu Liang, Rachata Ausavarungnirun, Qingan Li, Chun Jason Xue. ASPLOS 2024, Volume 3, pages 654 to 670. DOI 10.1145/3620666.3651377. Full text read from a copy hosted by one of the authors; that copy appears to predate the camera-ready version, since it still carries revision annotations in the margins.