Understanding File Descriptors and FD Leaks in Android
Android inherits Linux file descriptors, which are indexed by non‑negative integers and limited per process, and this article explains their kernel structures, common leak sources such as HandlerThread eventfd, unclosed streams, and SQLite cursor windows, plus practical commands and monitoring techniques to detect and fix FD leaks.
This article introduces the concept and working principle of file descriptors (FD) in Linux and Android, and analyzes common FD leak scenarios in Android applications.
File descriptors are non‑negative integers used by the kernel to index opened files. They can be inspected with commands such as ls -la /proc/$pid/fd and the system-wide limit with cat /proc/sys/fs/file-max . Android inherits the Linux FD system, with a default per‑process limit of 1024 on older kernels and up to 32768 on recent Android devices.
The kernel structures that represent a process’s FD table are shown, including struct task_struct , struct files_struct , and struct fdtable . The article provides the relevant source snippets:
struct task_struct { long state; struct mm_struct *mm; pid_t pid; struct task_struct *parent; struct list_head children; struct fs_struct *fs; struct files_struct *files; }; struct files_struct { atomic_t count; bool resize_in_progress; wait_queue_head_t resize_wait; struct fdtable __rcu *fdt; struct fdtable fdtab; spinlock_t file_lock ____cacheline_aligned_in_smp; unsigned int next_fd; unsigned long close_on_exec_init[1]; unsigned long open_fds_init[1]; unsigned long full_fds_bits_init[1]; struct file __rcu *fd_array[NR_OPEN_DEFAULT]; }; struct fdtable { unsigned int max_fds; struct file __rcu **fd; unsigned long *close_on_exec; unsigned long *open_fds; unsigned long *full_fds_bits; struct rcu_head rcu; };Three main FD leak categories are discussed:
HandlerThread leaks : A HandlerThread creates a Looper, which internally creates an eventfd . If the thread is not properly shut down (e.g., quitSafely() is not called), the associated FD remains open. The article shows the Java and native code that allocate the FD.
IO leaks : Improper handling of streams (e.g., not closing FileOutputStream in a finally block) can leave native FDs open. The native path goes through FileOutputStream_open0 , fileOpen , and ultimately open64 , which returns a new FD.
SQLite leaks : Cursor objects allocate a native CursorWindow , which creates an ashmem region via ashmem_create_region , producing an FD. Failing to close the cursor leads to FD accumulation.
Additional leak sources include InputChannel creation when adding views repeatedly via WindowManager.addView . The InputChannel pair is created with socketpair , each end being a file descriptor.
The article also provides practical debugging steps:
Print current FD usage with ls -la /proc/$pid/fd and identify which FD types dominate.
Use dumpsys window to detect abnormal windows related to InputChannel leaks.
Implement runtime monitoring that periodically checks the FD count and logs detailed information when a threshold is exceeded.
References to Linux kernel source, Android source, inode tutorials, and other documentation are listed at the end.
vivo Internet Technology
Sharing practical vivo Internet technology insights and salon events, plus the latest industry news and hot conferences.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.