Memory leaks are a common problem during Android Native layer development. Memory leaks not only cause the application to consume more and more memory, but also may cause performance issues and crashes. Therefore, detecting and solving memory leaks is crucial to ensure the stability and performance of your application. In this article, we will introduce four options for detecting memory leaks in the Android Native layer in detail, and analyze their advantages and disadvantages as well as applicable scenarios.
I. Scheme for detecting memory leaks at the Android Native layer
1.1 AddressSanitizer (ASan)
1.1.1 Introduction to the Principles
AddressSanitizer (ASan for short) is a memory error detector that detects a variety of memory-related errors, including memory leaks. In Android NDK, we can enable ASan by adding -fsanitize=address
to the compile options. ASan will monitor memory operations while the program is running, and when a memory leak is detected, it will print out a detailed error message, including the size, location and stack information of the leak.
Principles of AddressSanitizer:
Memory Layout Transformations: ASan changes the memory layout of a program at compile time, so that each object (variable, array, etc.) in the program is surrounded by some additional “redzones”. These redzones are used to detect out-of-bounds memory accesses. For example, if an array is accessed outside its boundaries and accesses a red zone, ASan reports a buffer overflow error.
Shadow Memory: ASan uses shadow memory to keep track of the state of each byte of memory in a program. Shadow memory is a map of a program’s memory used to store metadata about the state of the memory, such as whether the memory has been allocated, initialized, etc. When a program accesses memory, ASan checks the corresponding shadow memory to determine if the access is legitimate.
Compiler Staking: ASan inserts checking code into the program through compiler staking (instrumentation). These checks are executed when memory accesses occur to detect potential memory errors. For example, ASan inserts code into heap allocation and freeing functions such asmalloc
andfree
to detect memory leaks and the use of freed memory.
Official documentation: developer.android.google.co.uk/ndk/guides/… github.com/google/sani…
1.1.2 Advantages, disadvantages and scenarios of use
- Faster detection and less runtime performance overhead.
Can detect a variety of memory errors, including memory leaks, out-of-bounds reads and writes, etc.
Provides detailed error information, including the size, location, and stack information of the leak.
- Requires recompilation of the program, which may result in increased compilation time.
- It may cause the program to take up more memory.
Usage Scenario: Suitable for use in the development and testing phases, not suitable for use in online environments.
1.2 LeakSanitizer (LSan)
1.2.1 Introduction to the Principles
LeakSanitizer (LSan for short) is a specialized tool for detecting memory leaks, which can detect unfree memory in a program. Similar to ASan, we can enable LSan by adding -fsanitize=leak
to the compile options. LSan will check for all unfree memory when the program exits, and print out a detailed error message if a memory leak is detected.
1.2.2 Advantages, disadvantages and scenarios of use
- Specialized for detecting memory leaks with high accuracy.
- Low runtime performance overhead.
- The program needs to be recompiled.
- Can only detect memory leaks, not other memory errors.
Usage Scenario: Suitable for use in the development and testing phases, not suitable for use in online environments.
1.3 Valgrind
1.3.1 Introduction to the Principles
Valgrind is a powerful memory debugging tool that can detect a variety of memory-related errors, such as memory leaks, use of uninitialized memory, memory access out-of-bounds, and so on. However, Valgrind runs slowly, so it is usually used only in the development and debugging phases.
Valgrind uses a technique called Dynamic Binary Instrumentation (DBI) to detect memory errors. Specifically, Valgrind translates a program’s machine code into an Intermediate Representation (IR) at runtime, then inserts checking code on the IR, and finally translates the IR back into machine code and executes it.
1.3.2 Advantages, disadvantages and scenarios of use
Can detect a variety of memory errors, including memory leaks, out-of-bounds reads and writes, etc.- There is no need to recompile the program.
- Runs slower and has higher performance overhead.
Support for the Android platform is not as complete as ASan and LSan.
Usage Scenario: Suitable for use in the development and debugging phase, not suitable for use in online environments.
1.4 Manual detection
1.4.1 Introduction to the Principles
In addition to using tools, we can also detect memory leaks through manual inspection. For example, we can record information every time we allocate and free memory, and then periodically check this information to find out the memory that has not been freed.
In Android, to manually detect memory leaks in the Native layer, you can use the following methods:
Overloading Memory Allocation and Release Functions: You can overload the memory allocation and release functions such asmalloc
,calloc
,realloc
, andfree
to record information each time you allocate and release memory. For example, you can create a global memory allocation table to store all allocated memory blocks and their metadata (e.g. allocation size, allocation location, etc.). Then, when you free memory, remove the corresponding entries from the memory allocation table. Periodically, the memory allocation table is checked to find out the memory that has not been freed. The following is a simple example:
#include <map>
#include <mutex>
#include <cstdlib>
#include <cstring>
static std::map<void*, size_t> g_memory_map;
static std::mutex g_memory_map_mutex;
extern "C" void* malloc(size_t size) {
void* ptr = std::malloc(size);
if (ptr) {
std::unique_lock<std::mutex> lock(g_memory_map_mutex);
g_memory_map[ptr] = size;
}
return ptr;
}
extern "C" void free(void* ptr) {
if (ptr) {
std::unique_lock<std::mutex> lock(g_memory_map_mutex);
g_memory_map.erase(ptr);
}
std::free(ptr);
}
Regularly check the memory allocation table: you can check the memory allocation table at critical points in your program (e.g., on exit or at specific intervals) to find out what memory is not being freed. This can help you find memory leaks and determine where the leaks are occurring. Example:
void check_memory_leaks() {
std::unique_lock<std::mutex> lock(g_memory_map_mutex);
if (!g_memory_map.empty()) {
for (const auto& entry : g_memory_map) {
printf("Memory leak: address=%p, size=%zu\n", entry.first, entry.second);
}
} else {
printf("No memory leaks detected.\n");
}
}
Add Memory Leak Detection Function to the App: You can also add a specialized memory leak detection function to the app, such as a button or a command. When the user triggers this function, the app will perform memory leak detection and display the result to the user or output it to the log. In this way, you can check for memory leaks at any time while running the app.
Please note that manually detecting memory leaks may increase the runtime overhead of your program and may lead to some thread-safety related issues. When using this method, you need to make sure that your code is thread-safe and perform memory leak detection without affecting the performance of your program. Also, manual memory leak detection may not find all memory leaks, so it is recommended that you also use other tools (such as AddressSanitizer, LeakSanitizer, or Valgrind) to assist in detecting memory leaks.
1.4.2 Advantages, disadvantages and scenarios of use
- Less impact on the performance of the program.
- Detection strategies can be customized to meet specific needs.
- Accuracy and efficiency may not be as good as specialized testing tools.
- Testing code needs to be written and maintained manually.
Usage scenarios: suitable for use in development, testing and online environments, but need to be combined with other detection tools to improve the detection effect.
ii. recommendations for practice
In real projects, we can combine multiple memory leak detection schemes to improve detection. Here are some suggestions:
Coding norms: Following certain coding norms and best practices when writing code, such as using smart pointers and avoiding circular references, can effectively reduce the risk of memory leaks.
Code Review: During the development process, code review is conducted regularly to check whether there is any potential risk of memory leakage in the code. Code review can help us find and fix problems in time and improve code quality.
Automated testing: Introduce automated testing in the project to detect memory leaks for critical features. Tools such as ASan, LSan, etc. can be used in Continuous Integration (CI) environments to detect memory leaks and ensure that new commits do not introduce new memory leaks.
Performance monitoring: In the online environment, the memory usage of the application is monitored periodically. If abnormal memory usage is found, you can use manual detection methods or feed the problem back to the development environment for further analysis and processing using other tools.
Problem localization: When a memory leak problem is detected, based on the error information provided by the tool, we can quickly locate where the problem occurs. Combined with stack information, relative address, etc., it can help us better understand the cause of the problem and thus fix it.
III. Summary
During the development and testing phase, we can use tools such as ASan, LSan and Valgrind to detect memory leaks. In the online environment, however, these tools are not suitable for direct use due to their high performance overhead. In this case, we can use manual detection methods combined with code review and good programming habits to minimize the occurrence of memory leaks.
However, it is important to note that these tools are not guaranteed to detect all memory leaks. Memory leak detection and fixing requires an in-depth understanding of our code, as well as good programming habits. Only then can we effectively prevent and fix memory leaks, thus improving the stability and performance of our applications.