关于Java中几种loadClass的讨论

java中有几类加载class的方法,本文针对这几个方法,就行源码导读。

 

本文的native源码来源于android4.1.2源码。

 

1. Class.forName(className, true, classLoader);
clazz = Class.forName(className, true, classLoader);
className:要加载的类的全路径名。

classLoader:类加载器。

这个函数会调用native方法,源码:java_lang_Class.cpp
static void Dalvik_java_lang_Class_classForName(const u4* args, JValue* pResult)

{

    StringObject* nameObj = (StringObject*) args[0];

    bool initialize = (args[1] != 0);

    Object* loader = (Object*) args[2];


    RETURN_PTR(dvmFindClassByName(nameObj, loader, initialize));

}



这里调用的是dvmFindClassByName(nameObj, loader, initialize),重点在这句,由于此函数代码比较多,我只罗列了关键语句:

ClassObject* dvmFindClassByName(StringObject* nameObj, Object* loader,

    bool doInit)

{

    ClassObject* clazz = NULL;

    char* name = NULL;

    char* descriptor = NULL;

    ......

// 初始化的参数在这里体现

    if (doInit)

        clazz = dvmFindClass(descriptor, loader);

    else

        clazz = dvmFindClassNoInit(descriptor, loader);


    .......


}



由于dvmFindClass会先调用dvmFindClassNoInit,下面优先分析dvmFindClassNoInit

由于代码比较多,下面只列出了调用函数关系:

dvmFindClassNoInit-->findClassFromLoaderNoInit-->dvmLookupClass-->dvmHashTableLookup


关键看dvmHashTableLookup这个函数:


void* dvmHashTableLookup(HashTable* pHashTable, u4 itemHash, void* item,

   HashCompareFunc cmpFunc, bool doAdd)
{

    pEntry = &pHashTable->pEntries[itemHash & (pHashTable->tableSize-1)];
    pEnd = &pHashTable->pEntries[pHashTable->tableSize];
    while (pEntry->data != NULL) {
        if (pEntry->data != HASH_TOMBSTONE &&
            pEntry->hashValue == itemHash &&
            (*cmpFunc)(pEntry->data, item) == 0)  // 这里是需要ClassObject的关键点
    }

    return result;
}

此函数寻找ClassObject的逻辑是:

通过classname生成hash值,在HashTable中寻找此类的ClassObject,而这个HashTable保存在全局变量DvmGlobals中,ClassObject也就是C++中的对象,在虚拟机的堆中,classname和classloader两者对应唯一的一个ClassObject。看一下关键的比较函数cmpFunc如何判定此ClassObject是classname要寻找的Object

 

match = (strcmp(clazz->descriptor, pCrit->descriptor) == 0 &&
             (clazz->classLoader == pCrit->loader ||
              (pCrit->loader != NULL &&
 
               dvmLoaderInInitiatingList(clazz, pCrit->loader)) ));

这里通过clazz->descriptor,classLoader 决定是否是要寻找的类对象。

 

所以有关java中ClassLoader内容,不同的类在不同的ClassLoader中加载,对应的不同的ClassObject,有此函数可以得出此结论。

 

回到刚才的函数,如果通过dvmLookupClass寻找不到对应的ClassObject,怎么处理?

会调用Invoke loadClass()进行class加载和初始化。

 

     

const Method* loadClass =
            loader->clazz->vtable[gDvm.voffJavaLangClassLoader_loadClass];
        JValue result;
        dvmCallMethod(self, loadClass, loader, &result, nameObj);
        clazz = (ClassObject*) result.l;

 

这段函数还是比较重要的,通过ClassLoader,进行重新加载,dvmCallMethod是java调用native的入口。

 

 

2. ClassLoader Class<?> loadClass(String className, boolean resolve)

 

这个函数可能会被表面定义混淆,这个函数的逻辑也是先在HashTable中需要className对应的ClassObjet,如果寻找不到,在进行加载和初始化。

 

 

protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
        Class<?> clazz = findLoadedClass(className);
 
        if (clazz == null) {
            try {
                clazz = parent.loadClass(className, false);
            } catch (ClassNotFoundException e) {
                // Don't want to see this.
            }
 
            if (clazz == null) {
                clazz = findClass(className);
            }
        }
 
        return clazz;
    }

 

一、首先看Class<?> clazz = findLoadedClass(className);这句

 

对应的native源码在java_lang_VMClassLoader.cpp中。

 

static void Dalvik_java_lang_VMClassLoader_loadClass(const u4* args,
    JValue* pResult)
{
    StringObject* nameObj = (StringObject*) args[0];
    bool resolve = (args[1] != 0);
    ClassObject* clazz;
 
    clazz = dvmFindClassByName(nameObj, NULL, resolve);
    assert(clazz == NULL || dvmIsClassLinked(clazz));
    RETURN_PTR(clazz);
}

 

重点看一下dvmFindClassByName

 

ClassObject* dvmFindClassByName(StringObject* nameObj, Object* loader,
    bool doInit)
{
    ClassObject* clazz = NULL;
    char* name = NULL;
    char* descriptor = NULL;
    if (doInit)  // 这里是这个函数的重点逻辑
        clazz = dvmFindClass(descriptor, loader);
    else
        clazz = dvmFindClassNoInit(descriptor, loader);
}

 

这里如果查找不到,便会返回null。

 

二、clazz = parent.loadClass(className, false);

这里是调用classloader的父类进行加载,其加载逻辑相同。

 

三、 clazz = findClass(className);

如果一和二都不能找到className对应的ClassObject,那么就要通过findClass(className)进行寻找,这个函数的定义在classLoader,而android平台下定义在BaseDexClassLoader,看一下他的函数:

protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class clazz = pathList.findClass(name);
 
        if (clazz == null) {
            throw new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + originalPath);
        }
 
        return clazz;
    }

 

这里调用的是pathList.findClass(name);

 

继续跟踪函数:

   

public Class findClass(String name) {
        for (Element element : dexElements) {
            DexFile dex = element.dexFile;
 
            if (dex != null) {
                Class clazz = dex.loadClassBinaryName(name, definingContext);
                if (clazz != null) {
                    return clazz;
                }
            }
        }

 

        return null;

 

    }

 

这里调用的是dex.loadClassBinaryName(name, definingContext);,对应的native函数为:

dalvik_system_DexFile.cpp

 

static void Dalvik_dalvik_system_DexFile_defineClass(const u4* args,
    JValue* pResult)
{
    StringObject* nameObj = (StringObject*) args[0];
    Object* loader = (Object*) args[1];
    int cookie = args[2];
    ClassObject* clazz = NULL;
    DexOrJar* pDexOrJar = (DexOrJar*) cookie;
    DvmDex* pDvmDex;
    char* name;
    char* descriptor;
 
    name = dvmCreateCstrFromString(nameObj);
    descriptor = dvmDotToDescriptor(name);
    ALOGV("--- Explicit class load '%s' l=%p c=0x%08x",
        descriptor, loader, cookie);
    free(name);
 
    if (!validateCookie(cookie))
        RETURN_VOID();
 
    if (pDexOrJar->isDex)
        pDvmDex = dvmGetRawDexFileDex(pDexOrJar->pRawDexFile);
    else
        pDvmDex = dvmGetJarFileDex(pDexOrJar->pJarFile);
 
    /* once we load something, we can't unmap the storage */
    pDexOrJar->okayToFree = false;

 

    clazz = dvmDefineClass(pDvmDex, descriptor, loader);

 

 

}

 

 clazz = dvmDefineClass(pDvmDex, descriptor, loader);这句是核心,

直接进入Class.cpp中的static ClassObject* findClassNoInit(const char* descriptor, Object* loader,

    DvmDex* pDvmDex)函数,这个函数的简单逻辑是dvmLookupClass寻找class,如果找不到,会创建对应的ClassObject,并且添加到Globals中。

 

clazz = dvmLookupClass(descriptor, loader, true); // 寻找对应的ClassObject
    if (clazz == NULL) {
        const DexClassDef* pClassDef;
 
        dvmMethodTraceClassPrepBegin();
        profilerNotified = true;
 
#if LOG_CLASS_LOADING
        u8 startTime = dvmGetThreadCpuTimeNsec();
#endif
 
// 通过dex加载对应的类
        if (pDvmDex == NULL) {
            assert(loader == NULL);     /* shouldn't be here otherwise */
            pDvmDex = searchBootPathForClass(descriptor, &pClassDef);
        } else {
            pClassDef = dexFindClass(pDvmDex->pDexFile, descriptor);
        }
 
        if (pDvmDex == NULL || pClassDef == NULL) {
            if (gDvm.noClassDefFoundErrorObj != NULL) {
                /* usual case -- use prefabricated object */
                dvmSetException(self, gDvm.noClassDefFoundErrorObj);
            } else {
                /* dexopt case -- can't guarantee prefab (core.jar) */
                dvmThrowNoClassDefFoundError(descriptor);
            }
            goto bail;
        }
 
        /* found a match, try to load it */
        clazz = loadClassFromDex(pDvmDex, pClassDef, loader);
        if (dvmCheckException(self)) {
            /* class was found but had issues */
            if (clazz != NULL) {
                dvmFreeClassInnards(clazz);
                dvmReleaseTrackedAlloc((Object*) clazz, NULL);
            }
            goto bail;
        }
 
        /*
         * Lock the class while we link it so other threads must wait for us
         * to finish.  Set the "initThreadId" so we can identify recursive
         * invocation.  (Note all accesses to initThreadId here are
         * guarded by the class object's lock.)
         */
        dvmLockObject(self, (Object*) clazz);
        clazz->initThreadId = self->threadId;
 
        /*
         * Add to hash table so lookups succeed.
         *
         * [Are circular references possible when linking a class?]
         */
        assert(clazz->classLoader == loader);
 if (!dvmAddClassToHash(clazz)) { 添加创建的类到HashTable
            /*
             * Another thread must have loaded the class after we
             * started but before we finished.  Discard what we've
             * done and leave some hints for the GC.
             *
             * (Yes, this happens.)
             */
            //ALOGW("WOW: somebody loaded %s simultaneously", descriptor);
            clazz->initThreadId = 0;
            dvmUnlockObject(self, (Object*) clazz);
 
            /* Let the GC free the class.
             */
            dvmFreeClassInnards(clazz);
            dvmReleaseTrackedAlloc((Object*) clazz, NULL);
 
            /* Grab the winning class.
             */
            clazz = dvmLookupClass(descriptor, loader, true);
            assert(clazz != NULL);
            goto got_class;
        }
        dvmReleaseTrackedAlloc((Object*) clazz, NULL);
......
}

 

到此整个流程已经介绍完毕。

 

由此看出我们直接调用ClassLoader.loadClass要比调用Class.forName更高效。