Several techniques can be used to find and isolate native code memory leaks. In general there is no single ideal solution for all platforms. The following are some techniques to diagnose leaks in native code.
A very common practice is to track all allocation and free calls of the native allocations. This can be a fairly simple process or a very sophisticated one. Many products over the years have been built up around the tracking of native heap allocations and the use of that memory.
Tools like IBM Rational Purify and the runtime checking functionality of Sun Studio dbx
debugger can be used to find these leaks in normal native code situations and also find any access to native heap memory that represents assignments to uninitialized memory or accesses to freed memory. See Find Leaks with dbx Debugger.
Not all these types of tools will work with Java applications that use native code, and usually these tools are platform-specific. Because the virtual machine dynamically creates code at runtime, these tools can wrongly interpret the code and fail to run at all, or give false information. Check with your tool vendor to ensure that the version of the tool works with the version of the virtual machine you are using.
See sourceforge for many simple and portable native memory leak detecting examples. Most of these libraries and tools assume that you can recompile or edit the source of the application and place wrapper functions over the allocation functions. The more powerful of these tools allow you to run your application unchanged by interposing over these allocation functions dynamically. This is the case with the library libumem.so
first introduced in Oracle Solaris 9 operating system update 3; see Find Leaks with libumem Tool.
If you write a JNI library, then consider creating a localized way to ensure that your library does not leak memory, by using a simple wrapper approach.
The procedure in Example 3-7 is an easy localized allocation tracking approach for a JNI library. First, define the following lines in all source files.
Example 3-7 Define this Procedure in Source Files
#include <stdlib.h> #define malloc(n) debug_malloc(n, __FILE__, __LINE__) #define free(p) debug_free(p, __FILE__, __LINE__)
Then you can use the functions in Example 3-8 to watch for leaks.
Example 3-8 Function to Watch Leaks
/* Total bytes allocated */ static int total_allocated; /* Memory alignment is important */ typedef union { double d; struct {size_t n; char *file; int line;} s; } Site; void * debug_malloc(size_t n, char *file, int line) { char *rp; rp = (char*)malloc(sizeof(Site)+n); total_allocated += n; ((Site*)rp)->s.n = n; ((Site*)rp)->s.file = file; ((Site*)rp)->s.line = line; return (void*)(rp + sizeof(Site)); } void debug_free(void *p, char *file, int line) { char *rp; rp = ((char*)p) - sizeof(Site); total_allocated -= ((Site*)rp)->s.n; free(rp); }
The JNI library would then need to periodically (or at shutdown) check the value of the total_allocated
variable to verify that it made sense. The preceding code could also be expanded to save in a linked list the allocations that remained and report where the leaked memory was allocated. This is a localized and portable way to track memory allocations in a single set of sources. You would need to ensure that debug_free()
was called only with the pointer that came from debug_malloc()
, and you would also need to create similar functions for realloc()
, calloc()
, strdup()
, and so forth, if they were used.
A more global way to look for native heap memory leaks would involve interposition of the library calls for the entire process.
Most operating systems include some form of global allocation tracking support.
On Windows, search the MSDN library for debug support. The Microsoft C++ compiler has the /Md
and /Mdd
compiler options that will automatically include extra support for tracking memory allocation.
Linux systems have tools such as mtrace
and libnjamd
to help in dealing with allocation tracking.
Oracle Solaris operating system provides the watchmalloc
tool. Oracle Solaris 9 operating system update 3 also introduced the libumem
tool, see Find Leaks with libumem Tool.
The dbx
debugger includes the Runtime Checking (RTC) functionality, which can find leaks. The dbx
debugger is part of Oracle Solaris Studio and also available for Linux.
Example 3-9 shows a sample dbx
session.
Example 3-9 A Sample dbx Session
$dbx ${java_home}/bin/java
Reading java Reading ld.so.1 Reading libthread.so.1 Reading libdl.so.1 Reading libc.so.1 (dbx)dbxenv rtc_inherit on
(dbx)check -leaks
leaks checking - ON (dbx)run HelloWorld
Running: java HelloWorld (process id 15426) Reading rtcapihook.so Reading rtcaudit.so Reading libmapmalloc.so.1 Reading libgen.so.1 Reading libm.so.2 Reading rtcboot.so Reading librtc.so RTC: Enabling Error Checking... RTC: Running program... dbx: process 15426 about to exec("/net/bonsai.sfbay/export/home2/user/ws/j2se/build/solaris-i586/bin/java") dbx: program "/net/bonsai.sfbay/export/home2/user/ws/j2se/build/solaris-i586/bin/java" just exec'ed dbx: to go back to the original program use "debug $oprog" RTC: Enabling Error Checking... RTC: Running program... t@1 (l@1) stopped in main at 0x0805136d 0x0805136d: main : pushl %ebp (dbx)when dlopen libjvm { suppress all in libjvm.so; }
(2) when dlopen libjvm { suppress all in libjvm.so; } (dbx)when dlopen libjava { suppress all in libjava.so; }
(3) when dlopen libjava { suppress all in libjava.so; } (dbx) cont Reading libjvm.so Reading libsocket.so.1 Reading libsched.so.1 Reading libCrun.so.1 Reading libm.so.1 Reading libnsl.so.1 Reading libmd5.so.1 Reading libmp.so.2 Reading libhpi.so Reading libverify.so Reading libjava.so Reading libzip.so Reading en_US.ISO8859-1.so.3 hello world hello world Checking for memory leaks... Actual leaks report (actual leaks: 27 total size: 46851 bytes) Total Num of Leaked Allocation call stack Size Blocks Block Address ========== ====== =========== ======================================= 44376 4 - calloc < zcalloc 1072 1 0x8151c70 _nss_XbyY_buf_alloc < get_pwbuf < _getpwuid < GetJavaProperties < Java_java_lang_System_initProperties < 0xa740a89a< 0xa7402a14< 0xa74001fc 814 1 0x8072518 MemAlloc < CreateExecutionEnvironment < main 280 10 - operator new < Thread::Thread 102 1 0x8072498 _strdup < CreateExecutionEnvironment < main 56 1 0x81697f0 calloc < Java_java_util_zip_Inflater_init < 0xa740a89a< 0xa7402a6a< 0xa7402aeb< 0xa7402a14< 0xa7402a14< 0xa7402a14 41 1 0x8072bd8 main 30 1 0x8072c58 SetJavaCommandLineProp < main 16 1 0x806f180 _setlocale < GetJavaProperties < Java_java_lang_System_initProperties < 0xa740a89a< 0xa7402a14< 0xa74001fc< JavaCalls::call_helper < os::os_exception_wrapper 12 1 0x806f2e8 operator new < instanceKlass::add_dependent_nmethod < nmethod::new_nmethod < ciEnv::register_method < Compile::Compile #Nvariant 1 < C2Compiler::compile_method < CompileBroker::invoke_compiler_on_method < CompileBroker::compiler_thread_loop 12 1 0x806ee60 CheckJvmType < CreateExecutionEnvironment < main 12 1 0x806ede8 MemAlloc < CreateExecutionEnvironment < main 12 1 0x806edc0 main 8 1 0x8071cb8 _strdup < ReadKnownVMs < CreateExecutionEnvironment < main 8 1 0x8071cf8 _strdup < ReadKnownVMs < CreateExecutionEnvironment < main
The output shows that the dbx
debugger reports memory leaks if memory is not freed at the time the process is about to exit. However, memory that is allocated at initialization time and needed for the life of the process is often never freed in native code. Therefore, in such cases the dbx
debugger can report memory leaks that are not leaks in reality.
Note: Example 3-9 used two suppress
commands to suppress the leaks reported in the virtual machine, libjvm.so
and the Java support library, libjava.so
.
First introduced in Oracle Solaris 9 operating system update 3, the libumem.so
library and the modular debugger mdb
can be used to debug memory leaks. Before using libumem
, you must preload the libumem
library and set an environment variable as shown in Example 3-10.
Example 3-10 Set an Environment Variable for libumem
$ LD_PRELOAD=libumem.so $ export LD_PRELOAD $ UMEM_DEBUG=default $ export UMEM_DEBUG
Now, run the Java application, but stop it before it exits. Example 3-11 uses truss
to stop the process when it calls the _exit
system call.
At this point you can attach the mdb
debugger, as shown in Example 3-12.
The ::findleaks
command is the mdb
command to find memory leaks. If a leak is found, this command prints the address of the allocation call, buffer address, and nearest symbol.
It is also possible to get the stack trace for the allocation that resulted in the memory leak by dumping the bufctl
structure. The address of this structure can be obtained from the output of the ::findleaks
command.
See analyzing memory leaks using libumem
for troubleshooting the cause for a memory leak.