I am using some native libraries in my Android application and I want to unload them from the memory at some point in time. Libraries get unloaded when ClassLoader that loaded class that loaded native libraries gets garbage collected. Inspiration: native unloading.
In the code below I simplify the case by omitting a native library loading code, just Classloader memory leak is demonstrated.
I am testing this on the Android KitKat 4.4.2, API 19. Device: Motorola Moto G.
For the demonstration I have the following ClassLoader, derived from PathClassLoader
used for loading Android applications.
package com.demo;
import android.util.Log;
import dalvik.system.PathClassLoader;
public class LibClassLoader extends PathClassLoader {
private static final String THIS_FILE="LibClassLoader";
public LibClassLoader(String dexPath, String libraryPath, ClassLoader parent) {
super(dexPath, libraryPath, parent);
}
@Override
protected void finalize() throws Throwable {
Log.v(THIS_FILE, "Finalizing classloader " + this);
super.finalize();
}
}
I have the EmptyClass
to load with the LibClassLoader
.
package com.demo;
public class EmptyClass {
}
And the memory leak is caused in the following code:
final Context ctxt = this.getApplicationContext();
PackageInfo pinfo = ctxt.getPackageManager().getPackageInfo(ctxt.getPackageName(), 0);
LibClassLoader cl2 = new LibClassLoader(
pinfo.applicationInfo.publicSourceDir,
pinfo.applicationInfo.nativeLibraryDir,
ClassLoader.getSystemClassLoader()); // Important: parent cannot load EmptyClass.
if (memoryLeak){
Class<?> eCls = cl2.loadClass(EmptyClass.class.getName());
Log.v("Demo", "EmptyClass loaded: " + eCls);
eCls=null;
}
cl2=null;
// Try to invoke GC
System.runFinalization();
System.gc();
Thread.sleep(250);
System.runFinalization();
System.gc();
Thread.sleep(500);
System.runFinalization();
System.gc();
Debug.dumpHprofData("/mnt/sdcard/hprof"); // Dump heap, hardcoded path...
The important thing to note is that parent of the cl2
is not ctxt.getClassLoader()
, the classloader that loaded the demonstration code class. This is by design because we don't want cl2
to use it's parent to load the EmptyClass
.
The thing is that if memoryLeak==false
, then cl2
gets garbage collected. If memoryLeak==true
, memory leak appears. This behavior is not consistent with observations on standard JVM (I used class loader from [1] to simulate the same behavior). On JVM in both cases cl2
gets garbage collected.
I also analyzed heap dump file with Eclipse MAT, cl2
was not garbage collected because class EmptyClass
still holds reference on it (as classes hold references on their class loaders). This makes sense. But EmptyClass
was not garbage collected from no reason, apparently. Path to GC root is just this EmptyClass
. I haven't managed to persuade GC to finalize EmptyClass
.
HeapDump file for memoryLeak==true
can be found here, Eclipse Android project with a demonstration application for this memory leak here.
I tried also another variations of loading the EmptyClass
in the LibClassLoader
, namely Class.forName(...)
or cl2.findClass()
. With/without static initialization, result was always the same.
I checked a lot of online resources, there are no static caching fields involved, as far as I know. I checked source codes of the PathClassLoader
and it's parent classes and I found nothing problematic.
I would be very thankful for insights and any help.
System.gc()
is only a hint to execute GC for the JVM/Dalvik. I am just wondering why there is a memory leak.To make it more clear as Erik Hellman wrote, I am speaking about loading NDK compiled C/C++ library, dynamically linked, with .so suffix.
Classloader Leaks. Especially in application servers and OSGi containers, there is another form of memory leak: the classloader leak. As classes are referenced by their classloaders, they get removed when the classloader is garbage-collected. That will happen only when the application gets unloaded.
This class loader is used to load classes and resources from a search path of URLs referring to both JAR files and directories. A class loader is an object that is responsible for loading classes. The class ClassLoader is an abstract class.
Class Loaders are a functional component of JVM, which loads class data from the '. class' file or from over the network in to the Method Area in Heap.
Class loader memory leakClasses with the same name can be loaded multiple times in a single JVM, each in a different class loader. Each web application gets its own class loader and this is what WebSphere® Application Server uses for isolating applications.
First, lets sort out the terminology here.
Is it a native library with JNI bindings that you want to load? That is, a file with the suffix .so that is implemented in C/C++ using the Android NDK? That is usually what we refer to when we talk about native library. If this is the case, then the only way to solve this is to run the library in separate process. Easiest way to do this is to create an Android service where you add android:process=":myNativeLibProcess"
for the entry in the manifest. This Service will then call System.loadLibrary()
as usual and you bind to the Service from your main process using Context.bindService()
.
If it is a set of Java classes inside a JAR file, then we are looking at something else. For Android, you need to create compile your library code into a DEX file that is placed into a JAR file and loaded using a DexClassLoader
, similar to what you've done in your code.
When you want to unload the library, you need to release all the references to the instances you've created AND the class loader used for loading the library. This will allow you to load a new version of the library later on. The only problem with this is that you won't reclaim all the memory used by the unloaded library on devices with API level 19 and lower (i.e., Android versions using Dalvik VM) because class definitions are not garbage collected. For Lollipop and later, the new VM will also garbage collect class definitions, so for these devices this will work better.
Hope that helps.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With