Memory leaks are one of the most critical issues for any android app, and identifying and analyzing C++ memory leaks on the Android platform can be daunting for developers. The AMAP app contains a huge volume of C++ code to manage the high-performance requirements posed by map rendering, navigation, and other core functions. To address the C++ memory leak challenges, AMAP's technical team has developed a unique solution based on practical experience to ensure better product quality.
Allocation function statistics and stack backtrace are the core means of identifying and analyzing C++ memory leaks. To resolve C++ memory leaks, it is imperative to determine both, the memory allocation point and call stack, otherwise, the problem gets complicated and the costs of the resolution also rise.
In Android, the malloc_debug module of Bionic comprehensively monitors and collects statistics regarding the memory allocation function, however, stack backtrace is inefficient. As Android is developed, Google provides a few methods for analyzing stack backtrace, but these methods also witness the following obstacles:
Therefore, it is extremely important to implement efficient stack backtrace and build a systematic Android native memory analysis system. Consequently, AMAP's improved and extended capabilities help to promptly identify and solve C++ memory leaks through automatic testing. This greatly improves development efficiency and reduces troubleshooting costs.
The Android platform uses Libunwind to perform stack backtrace, which usually meets the needs in most scenarios. However, global locks and unwind table parsing in the Libunwind implementation may result in performance loss and further renders application unresponsive and unavailable in case of frequent multithreaded calls.
A compiler has the -finstrument-functions compilation option that supports the insertion of user-defined functions (UDFs) in the beginning and at the end of a function.
It inserts calls to __cyg_profile_func_enter at the beginning of each function and inserts calls to __cyg_profile_func_exit at the end of the function. The two UDFs acquire call addresses, which can be recorded for acquiring the function call stack.
Sample results of instrumentation are illustrated in the image below:
Functions that require no instrumentation can be declared to the compiler through __attribute__((no_instrument_function)) function.
Information about calls can be recorded to ensure that the information is read by different threads without any impact. The recording can be implemented through thread synchronization. For example, you can add critical sections or mutexes when the involved variable is read or written, but this method lowers the efficiency.
Another method is to add locks based on thread-local storage (TLS), which is a dedicated storage area and accessible only by threads in that area. TLS eliminates the thread security issue and meets the requirement of call information recording. Therefore, to accelerate stack backtrace, you can record the call stack through instrumentation by using a compiler and then store the call stack in TLS. Following are the steps to implement it effectively:
Record call addresses in TLS in the form of arrays and cursors to insert, remove, and acquire code as fast as possible.
Refer to the logical diagram below:
Following charts outline the performance comparison based on Google Benchmarking performed on the 5.1 operating system:
As presented in the preceding statistical charts, in single-thread mode, the TLS method acquires the call stack at a speed that is 10 times faster than Libunwind. In 10-thread mode, the TLS method acquires the call stack at a speed that is 50 to 60 times faster than Libunwind.
The primary advantage of this solution is the highly improved acquisition speed which meets the requirement of frequent stack backtrace.
On the other hand, increased code size due to instrumentation by using the compiler is a critical limitation of this approach. The lengthy code can only be used by memory test packages, and not online. However, this problem can be solved through continuous integration via a common library. A corresponding memory test library is created from each C++ project upon delivery.
After accelerating the acquisition of the memory allocation stack, you can troubleshoot native memory leaks by using Google-provided tools, such as DDMS and the adb shell am dumpheap -n pid /data/local/tmp/heap.txt command. However, such troubleshooting is inefficient and must be performed in the defined mobile phone environment.
To improve the efficiency of troubleshooting native memory leaks, we built a comprehensive system as described below:
XianYu Tech - May 11, 2021
Alibaba Clouder - December 22, 2020
hyj1991 - July 22, 2019
XianYu Tech - November 11, 2021
Alibaba Clouder - May 17, 2021
XianYu Tech - September 11, 2020
API Gateway provides you with high-performance and high-availability API hosting services to deploy and release your APIs on Alibaba Cloud products.Learn More
An all-in-one service for log-type dataLearn More
Log into an artificial intelligence for IT operations (AIOps) environment with an intelligent, all-in-one, and out-of-the-box log management solutionLearn More
OpenAPI Explorer allows you to call an API through its web interface or WebCLI, and view the entire process.Learn More
More Posts by amap_tech