CN

Android Systrace Basics - Binder and Lock Contention Explained

Word count: 2.8kReading time: 17 min
2019/12/06
loading

This is the tenth article in the Systrace series, primarily providing a brief introduction to Binder and lock information in Systrace. It covers the basic situation of Binder, the representation of Binder communication in Systrace, how to view Binder information, and analysis of lock contention in SystemServer.

The purpose of this series is to view the overall operation of the Android system from a different perspective using Systrace, while also providing an alternative angle for learning the Framework. Perhaps you’ve read many articles about the Framework but can never remember the code, or you’re unclear about the execution flow. Maybe from Systrace’s graphical perspective, you can gain a deeper understanding.

Version note (reviewed 2026-10-13): This article retains its 2019 Systrace examples. The Binder and Java-lock analysis remains useful, but the old viewer, Framework line numbers, and device timings are version-specific. Code comparisons use AOSP Android 10, android-10.0.0_r1; ART waiter counting was also checked against Android 9 and Android 16. The screenshots come from vendor software and do not establish an exact AOSP build. Use the linked Perfetto workflow for newer traces.

Table of Contents

Series Article Index

  1. Introduction to Systrace
  2. Systrace Basics - Prerequisites for Systrace
  3. Systrace Basics - Why 60 fps?
  4. Android Systrace Basics - SystemServer Explained
  5. Systrace Basics - SurfaceFlinger Explained
  6. Systrace Basics - Input Explained
  7. Systrace Basics - Vsync Explained
  8. Systrace Basics - Vsync-App: Detailed Explanation of Choreographer-Based Rendering Mechanism
  9. Systrace Basics - MainThread and RenderThread Explained
  10. Systrace Basics - Binder and Lock Contention Explained
  11. Systrace Basics - Triple Buffer Explained
  12. Systrace Basics - CPU Info Explained
  13. Systrace Smoothness in Action 1: Understanding Jank Principles
  14. Systrace Smoothness in Action 2: Case Analysis - MIUI Launcher Scroll Jank Analysis
  15. Systrace Smoothness in Action 3: FAQs During Jank Analysis
  16. Systrace Responsiveness in Action 1: Understanding Responsiveness Principles
  17. Systrace Responsiveness in Action 2: Responsiveness Analysis - Using App Startup as an Example
  18. Systrace Responsiveness in Action 3: Extended Knowledge on Responsiveness
  19. Systrace Thread CPU State Analysis Tips - Runnable
  20. Systrace Thread CPU State Analysis Tips - Running
  21. Systrace Thread CPU State Analysis Tips - Sleep and Uninterruptible Sleep

Binder Overview

This article maps to Perfetto 10: Binder scheduling and lock contention. In old Systrace, Binder analysis mainly used Flow Events, Binder slices, and lock-contention text to identify caller and callee. In Perfetto, the same relationship still matters, but the evidence is distributed across Binder slices, thread states, CPU scheduling, flow relationships, and SQL queries.

When analyzing Binder-related jank today, first use the old method to establish the call relationship: who called whom, how long the call took, and what the caller was waiting for. Then continue with the current Perfetto workflow: check whether the caller slept waiting for Binder return, whether the callee was running, whether it queued behind a SystemServer lock, whether the Binder thread pool was busy, and whether CPU was saturated at the same time. This separates “Binder is slow” into remote execution time, lock contention, Binder thread-pool queueing, and CPU scheduling delay.

Most inter-process communication in Android uses Binder. I won’t provide an over-explanation of Binder here. If you want a deep understanding of Binder’s implementation, I recommend reading these three articles:

  1. Understanding Android Binder Mechanism 1/3: Driver Layer
  2. Understanding Android Binder Mechanism 2/3: C++ Layer
  3. Understanding Android Binder Mechanism 3/3: Java Layer

I’m giving Binder and locks their own article because this is a path I often need to follow when investigating jank or slow responses. A synchronous remote call can pass a server-side delay back to its caller. If a SystemServer service is waiting for a Java lock, an app’s synchronous call may wait with it. The monitor contention events below describe Java object monitor contention recorded by ART, not a lock inside the Binder driver. Non-Binder threads such as android.display can contend for the same monitor.

A long dequeueBuffer can also involve unavailable buffers, GPU/fence dependencies, or consumer delays. Whether it crosses Binder and where the consumer lives depend on the OS version and BufferQueue topology. A long call alone does not establish that the SurfaceFlinger main thread caused the delay.

Remote synchronous calls wait for a reply, even with a void return type. Remote oneway calls do not wait for server completion, but still incur submission overhead and server-side queueing. Local AIDL calls can execute directly on the calling thread; oneway does not automatically move them to a pool. Nested synchronous Binder calls can also reenter an original thread in the call chain. See the AIDL calling rules and Binder threading model.

Here is a Binder architecture diagram from an article. This piece focuses on Systrace, so we’ll cover how Binder appears in Systrace without diving into its internal implementation.

Binder Call Diagram

This old-device example shows SystemServer communicating with Qualcomm’s vendor.qti.hardware.perf@2.0-service:

The annotated client and server intervals represent elapsed time, not necessarily CPU execution. Separate time before receipt, scheduling delay, lock waits, and execution using flows and thread states. The earlier Java/JNI architecture diagram does not cover every native/HIDL call path.

In the old viewer, ViewOptions → Flow Events displays relationships already captured in the trace. It cannot recover Binder events that were never collected.

Select a Binder slice for its details:

Here, Transaction Id=334303 correlates send and receive events. Calling PID=10661 is the sending thread’s TID; Calling tgid=1605 is its process ID. The attachment’s raw events contain both values. Dest Process=867 is the destination process. Dest Thread=0 at send time does not mean Linux thread 0 executes the call; follow the receive event to find the actual thread.

Flags=0x10 is TF_ACCEPT_FDS, allowing file descriptors in replies. It does not include TF_ONE_WAY=0x01, so this transaction is synchronous. Decode Code=0x6 against the correct interface and version; the viewer’s “Java Layer Dependent” label does not identify a Java method. Wall Duration=11.739 ms includes waits. Self Time=11.363 ms excludes child slices but is still elapsed time, not CPU time. See the Binder UAPI flag definitions.

The next sections examine Java lock information and waiting. After finding the threads, check the matching source to establish which object they actually contend for.

Lock Information in Systrace

monitor contention with owner Binder:1605_B (4667) at void com.android.server.wm.ActivityTaskManagerService.activityPaused(android.os.IBinder)(ActivityTaskManagerService.java:1733) waiters=2 blocking from android.app.ActivityManager$StackInfo com.android.server.wm.ActivityTaskManagerService.getFocusedStackInfo()(ActivityTaskManagerService.java:2064)

Let’s break the above message into two parts, using blocking as the dividing line.

Part 1 Interpretation

monitor contention with owner Binder:1605_B (4667) at void com.android.server.wm.ActivityTaskManagerService.activityPaused(android.os.IBinder)(ActivityTaskManagerService.java:1733) waiters=2

A monitor is the synchronization mechanism associated with a Java object. One thread holds it at a time, and that thread can reenter it. An instance synchronized method locks this; a static one locks the corresponding Class object; a synchronized block locks its expression’s object. “Two pools” is not a literal description of the mechanism. The Object.wait() wait set is distinct from contention when entering synchronized: wait() releases the monitor and must reacquire it before returning. See JLS 17.1–17.2.

Owner identifies the holding thread: Binder:1605_B, Linux TID 4667. The owner is a thread, not an object or a method.

at identifies ART’s recorded holder location. Android 10 records it on paths such as monitor acquisition or inflation; it is not a continuously updated stack trace. The holder might currently be waiting on another Binder call rather than executing that line on a CPU.

The screenshot names activityPaused at ActivityTaskManagerService.java:1733. The following AOSP Android 10 method provides a source comparison; its synchronized block is at line 1694, not the vendor screenshot’s line number:

1
2
3
4
5
6
7
8
9
10
11
@Override
public final void activityPaused(IBinder token) {
final long origId = Binder.clearCallingIdentity();
synchronized (mGlobalLock) {
ActivityStack stack = ActivityRecord.getStackLocked(token);
if (stack != null) {
stack.activityPausedLocked(token, false);
}
}
Binder.restoreCallingIdentity(origId);
}

The object is mGlobalLock. Other threads entering synchronized blocks on that same object must wait for its release and successfully acquire it. The owner can reenter. Exiting the synchronized block unlocks it, including an exceptional exit; the whole method need not have returned.

waiters is the monitor’s waiter counter sampled before registering this wait, excluding the current contender. Android 9/10 save num_waiters_ before incrementing it; Android 16 uses fetch_add, which returns the old value. In this example, waiters=2 corresponds to two earlier contenders plus android.bg, making three. This is neither a FIFO position nor a live count throughout the slice. ART’s Object.wait() path also uses the counter, so it cannot always be treated as the size of a pure monitor-entry queue.

Part 2 Interpretation

blocking from android.app.ActivityManager$StackInfo com.android.server.wm.ActivityTaskManagerService.getFocusedStackInfo()(ActivityTaskManagerService.java:2064)

This part identifies the blocked thread’s method: ActivityTaskManagerService.getFocusedStackInfo on android.bg. The AOSP Android 10 comparison is below. Its synchronized block is at line 2027; line 2064 belongs to the original device source:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public ActivityManager.StackInfo getFocusedStackInfo() throws RemoteException {
enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getStackInfo()");
long ident = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
ActivityStack focusedStack = getTopDisplayFocusedStack();
if (focusedStack != null) {
return mRootActivityContainer.getStackInfo(focusedStack.mStackId);
}
return null;
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}

This also locks mGlobalLock. Both methods contend for the same object; substituting ActivityManagerService.this would describe a different lock.

Summary

The record says that android.bg was blocked acquiring a monitor in getFocusedStackInfo. At recording time, its owner was Binder:1605_B, with the recorded holder location in activityPaused. The blocked thread can proceed only after the monitor becomes available and it successfully acquires it. The title is a snapshot; it does not establish that the same owner holds the monitor throughout the entire wait.

The original owner, holder location, counter, and blocked location are:

1
2
3
4
monitor contention with owner Binder:1605_B (4667)
at void com.android.server.wm.ActivityTaskManagerService.activityPaused(android.os.IBinder)(ActivityTaskManagerService.java:1733)
waiters=2
blocking from android.app.ActivityManager$StackInfo com.android.server.wm.ActivityTaskManagerService.getFocusedStackInfo()(ActivityTaskManagerService.java:2064)

Waiting for Lock Analysis

Let’s stay with this trace and work through the order of events: first Binder:1605_B’s own wait, then the threads it blocks after acquiring the monitor:

The selected slice actually shows Binder:1605_B waiting for android.anim, whose recorded holder location is WindowAnimator.animate, for about 0.050 ms. It later acquires the monitor, and other threads wait for it. Nested Binder calls also mean its holding interval need not be continuous Running time.

Lock Waiting

These three records belong to different threads; the table corrects copied thread names in the old annotations. The attachment’s raw ATrace Begin/End intervals give:

Waiting thread Blocked method waiters in title Wait slice wall duration
android.display (1683) ActivityMetricsLogger.checkVisibility 0 3.647 ms
android.anim (1684) WindowManagerService.relayoutWindow 1 2.559 ms
android.bg (1692) ActivityTaskManagerService.getFocusedStackInfo 2 3.835 ms

All three initially identify Binder:1605_B (4667) as the holder of mGlobalLock. The intervals overlap; their sum is not a single call’s latency. Waiting threads can sleep, become Runnable after a wakeup, and run again to attempt acquisition. Java blocking state and kernel scheduling state are different descriptions.

Lock Release

The annotated wakeup chain is present in the attachment: Binder:1605_B → android.display → android.anim → android.bg. A wakeup gives a thread an opportunity to run. By itself, it proves neither immediate acquisition nor a FIFO monitor policy.

The attachment also contains further contention visible in the figure. After its checkVisibility wait ends, android.display waits for android.anim in WindowProcessController.hasActivitiesOrRecentTasks for 3.808 ms. Later, android.anim waits for android.bg in Session.getInTouchMode for 3.148 ms. This is not a six-step sequence in which each thread waits once and then finishes. The original android.bg wait retains the Binder owner in its title, illustrating why that snapshot is not a complete ownership history.

Correlate slice boundaries, scheduling states, wakers, subsequent owners, and source code. The following is a separate example of longer SystemServer contention, with different process and owner IDs; it is not a continuation of the sequence above:

Seeing this many threads waiting on locks is pretty alarming. Concurrent lock waits can increase startup or interaction latency. This image does not establish that installing more apps or using a phone longer causes this problem, nor that all systems routinely have 7–10 waiters. Follow the holder to determine whether it is executing expensive work, waiting for remote Binder or I/O, or waiting for CPU time.

Reduce avoidable critical-section work and review cross-process calls made while holding locks, while preserving shared-state consistency. More Binder threads do not remove serialization on one monitor. Changing an interface to oneway changes completion and error semantics, so it is not a general fix.

For wakeup inspection, see Analyzing Thread Wakeup Information.

Related Code

Both excerpts come from AOSP android-10.0.0_r1, art/runtime/monitor.cc. They explain the old trace format and are not standalone compilable examples.

Monitor Info

PrettyContentionInfo assembles the owner, recorded holder location, and waiter count:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
std::string Monitor::PrettyContentionInfo(const std::string& owner_name,
pid_t owner_tid,
ArtMethod* owners_method,
uint32_t owners_dex_pc,
size_t num_waiters) {
Locks::mutator_lock_->AssertSharedHeld(Thread::Current());
const char* owners_filename;
int32_t owners_line_number = 0;
if (owners_method != nullptr) {
TranslateLocation(owners_method, owners_dex_pc, &owners_filename, &owners_line_number);
}
std::ostringstream oss;
oss << "monitor contention with owner " << owner_name << " (" << owner_tid << ")";
if (owners_method != nullptr) {
oss << " at " << owners_method->PrettyMethod();
oss << "(" << owners_filename << ":" << owners_line_number << ")";
}
oss << " waiters=" << num_waiters;
return oss.str();
}

Block Info

This is a contiguous excerpt from Monitor::Lock. Note that the count is saved before incrementing it, and the event is emitted only when ATrace is enabled and an owner is available:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
ArtMethod* owners_method = locking_method_;
uint32_t owners_dex_pc = locking_dex_pc_;
// Do this before releasing the lock so that we don't get deflated.
size_t num_waiters = num_waiters_;
++num_waiters_;

// If systrace logging is enabled, first look at the lock owner. Acquiring the monitor's
// lock and then re-acquiring the mutator lock can deadlock.
bool started_trace = false;
if (ATraceEnabled()) {
if (owner_ != nullptr) { // Did the owner_ give the lock up?
std::ostringstream oss;
std::string name;
owner_->GetThreadName(name);
oss << PrettyContentionInfo(name,
owner_->GetTid(),
owners_method,
owners_dex_pc,
num_waiters);
// Add info for contending thread.
uint32_t pc;
ArtMethod* m = self->GetCurrentMethod(&pc);
const char* filename;
int32_t line_number;
TranslateLocation(m, pc, &filename, &line_number);
oss << " blocking from "
<< ArtMethod::PrettyMethod(m) << "(" << (filename != nullptr ? filename : "null")
<< ":" << line_number << ")";
ATraceBegin(oss.str().c_str());
started_trace = true;
}
}

Android 16, android-16.0.0_r1, still emits monitor contention events, but uses atomic fetch_add and a different owner-information path. The Android 10 excerpt is not the current implementation. Missing events do not prove an absence of contention: check capture categories, instrumentation conditions, and data completeness.

For newer traces in Perfetto, associate the Binder endpoints, then align thread states and monitor contention in the same interval. The current standard library provides android.binder and android.monitor_contention. Tables and fields depend on the Trace Processor version, and relevant events must have been captured. Missing associations or parsed records remain unknown; do not turn them into zero latency or zero contention.

References

  1. Understanding Android Binder Mechanism 1/3: Driver Layer
  2. Understanding Android Binder Mechanism 2/3: C++ Layer
  3. Understanding Android Binder Mechanism 3/3: Java Layer

Attachments

The attachment is App_lunch.html.zip, containing an old Systrace HTML report. This review inspected its Binder, monitor contention, and scheduling events as text without executing embedded scripts. Interactive compatibility with current Chrome was not verified; use a viewer or import path that supports this format.
Click here to download the Systrace attachments related to the article

About Me && Blog

Below is my personal intro and related links. I look forward to exchanging ideas with fellow professionals. “When three walk together, one can always be my teacher!”

  1. Blogger Intro: Includes personal WeChat and WeChat group links.
  2. Blog Content Navigation: A guide for my blog content.
  3. Curated Excellent Blog Articles - Android Performance Optimization Must-Knows: Welcome to recommend projects/articles.
  4. Android Performance Optimization Knowledge Planet: The paid community stopped operating in 2026 and no longer accepts new members; this link preserves its history.

One walks faster alone, but a group walks further together.

Scan WeChat QR Code

CATALOG
  1. 1. Table of Contents
  • Series Article Index
  • Binder Overview
  • Binder Call Diagram
  • Lock Information in Systrace
    1. 1. Part 1 Interpretation
    2. 2. Part 2 Interpretation
    3. 3. Summary
  • Waiting for Lock Analysis
    1. 1. Lock Waiting
    2. 2. Lock Release
  • Related Code
    1. 0.1. Monitor Info
    2. 0.2. Block Info
  • References
  • Attachments
  • About Me && Blog