Mobile Development 13 min read

Deep Dive into Android ClassLoader and findLoadedClass Mechanism for Code Coverage

The article details a high‑performance, high‑stability Android code‑coverage technique that creates a surrogate ClassLoader, copies the target PathClassLoader’s private classTable pointer, and invokes findLoadedClass on this loader to query a class’s load state without triggering Android’s native optimization that would otherwise automatically load the class.

Amap Tech
Amap Tech
Amap Tech
Deep Dive into Android ClassLoader and findLoadedClass Mechanism for Code Coverage

In the article "Android高性能高稳定性代码覆盖率方案技术实践", the authors explain the underlying principles of their high‑performance, high‑stability code‑coverage solution for Android. They focus on how to query class‑loading status without triggering the class‑loading optimization that the Android runtime applies.

The solution creates a new ClassLoader and copies the classTable field from the target PathClassLoader . By invoking findLoadedClass on this surrogate loader, the method can query whether a class has already been loaded without causing the class to be loaded if it is not present.

ClassLoader basics

Android and Java both use ClassLoader for loading classes. In Java, calling ClassLoader.findLoadedClass via reflection is straightforward. Android, however, adds an extra native optimization layer that can automatically load a class when findLoadedClass returns null .

The core Java implementation of loadClass is:

//源码路径:/java/lang/ClassLoader.java 
protected Class
loadClass(String name, boolean resolve) throws ClassNotFoundException {
    Class
c = findLoadedClass(name);
    if (c == null) {
        try {
            if (parent != null) {
                c = parent.loadClass(name, false);
            } else {
                c = findBootstrapClassOrNull(name);
            }
        } catch (ClassNotFoundException e) { }
        if (c == null) {
            c = findClass(name);
        }
    }
    return c;
}

The findLoadedClass method itself is:

//源码路径:/java/lang/ClassLoader.java 
protected final Class
findLoadedClass(String name) {
    ClassLoader loader;
    if (this == BootClassLoader.getInstance())
        loader = null;
    else
        loader = this;
    return VMClassLoader.findLoadedClass(loader, name);
}

Native implementation on Android

On Android, VMClassLoader.findLoadedClass is a native method defined in java_lang_VMClassLoader.cc :

//源码路径:art/runtime/native/java_lang_VMClassLoader.cc 
static jclass VMClassLoader_findLoadedClass(JNIEnv* env, jclass, jobject javaLoader, jstring javaName) {
    // ...
    ObjPtr
c = VMClassLoader::LookupClass(cl, soa.Self(), descriptor.c_str(), descriptor_hash, loader);
    if (c != nullptr && c->IsResolved()) {
        return soa.AddLocalReference
(c);
    }
    // Fast‑path for PathClassLoader/DexClassLoader
    if (loader != nullptr) {
        c = VMClassLoader::FindClassInPathClassLoader(cl, soa, soa.Self(), descriptor.c_str(), descriptor_hash, hs.NewHandle(loader));
        if (c != nullptr) {
            return soa.AddLocalReference
(c);
        }
    }
    return nullptr;
}

The fast‑path comment explains the optimization:

// Hard‑coded performance optimization: We know that all failed libcore calls to findLoadedClass
// are followed by a call to the classloader to actually load the class.

Thus, when findLoadedClass fails, Android immediately loads the class via the native ClassLinker::DefineClass path, causing the method to return a non‑null result even for classes that were not previously loaded.

ClassTable lookup

The native lookup eventually reaches ClassLinker::LookupClass , which queries the ClassTable associated with a ClassLoader :

//源码路径:art/runtime/class_linker.cc 
ObjPtr
ClassLinker::LookupClass(Thread* self, const char* descriptor, size_t hash, ObjPtr
class_loader) {
    ClassTable* const class_table = ClassTableForClassLoader(class_loader);
    if (class_table != nullptr) {
        ObjPtr
result = class_table->Lookup(descriptor, hash);
        if (result != nullptr) {
            return result;
        }
    }
    return nullptr;
}

The ClassTable is obtained via:

//源码路径:art/runtime/class_linker.cc 
ClassTable* ClassLinker::ClassTableForClassLoader(ObjPtr
class_loader) {
    return class_loader == nullptr ? boot_class_table_.get() : class_loader->GetClassTable();
}

In Java, the classTable field of ClassLoader is a private long that points to this native structure:

//源码路径:/java/lang/ClassLoader.java 
public abstract class ClassLoader {
    // ...
    /** Pointer to the class table, only used from within the runtime. */
    private transient long classTable;
    // ...
}

Bypassing the optimization

The authors propose creating a fresh ClassLoader , copying the classTable pointer from the target PathClassLoader into it, and then calling findLoadedClass on this surrogate loader. Because the new loader is neither a PathClassLoader nor a DexClassLoader , the fast‑path does not trigger class loading; the method simply returns null when the class is not yet loaded.

This technique provides a reliable way to query the loading state of application‑specific classes without side effects, which is essential for building a stable and efficient code‑coverage collection system on Android.

Conclusion

The article demonstrates how deep knowledge of Android’s class‑loading internals—especially the native VMClassLoader and ClassTable —enables developers to implement a high‑performance, high‑stability code‑coverage solution. By leveraging a custom ClassLoader to access the underlying ClassTable , the solution avoids unwanted class‑loading optimizations and achieves accurate coverage data.

NativeCode CoverageAndroidreflectionruntimeClassLoader
Amap Tech
Written by

Amap Tech

Official Amap technology account showcasing all of Amap's technical innovations.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.