Android class loader

Posted by bigray on Sun, 12 Dec 2021 03:10:24 +0100

Class lifecycle

Loading phase

The loading phase can be subdivided as follows

  • Load binary stream of class
  • The data structure transformation transforms the static storage structure represented by the binary stream into the runtime data structure of the method area
  • Generate Java Lang. class object, as the access entry of various data of this class in the method area

Method of loading binary stream of class

  • Read from the zip package. Our common JAR and AAR dependencies
  • Dynamically generated at run time. Our common dynamic proxy technology is in Java reflect. Proxy uses ProxyGenerateProxyClass to generate binary stream of proxy for specific interface
verification

Verification is the first step in the connection phase. The purpose of this phase is to ensure that the information contained in the byte stream of the Class file meets the requirements of the current virtual machine and will not endanger the security of the virtual machine itself.

  1. File format verification: such as whether it starts with magic number 0xCAFEBABE, whether the major and minor version numbers are within the processing range of the current virtual machine, constant rationality verification, etc.
    This stage ensures that the input byte stream can be correctly parsed and stored in the method area, and the format meets the requirements of describing a Java type information.
  2. Metadata verification: whether there is a parent class, whether the inheritance chain of the parent class is correct, whether the abstract class implements all the methods required in its parent class or interface, and whether fields and methods conflict with the parent class.
    The second stage is to ensure that there is no metadata information that does not comply with the Java language specification.
  3. Bytecode verification: through the analysis of data flow and control flow, it is determined that the program semantics is legal and logical. For example, ensure that the jump instruction will not jump to the bytecode instruction outside the method body.
  4. Symbol reference validation: occurs during the parsing phase to ensure that symbol references can be converted into direct references.

Consider using the - Xverify:none parameter to turn off most of the class verification measures to shorten the time of virtual machine class loading.

prepare

Allocate memory for class variables and set the initial value of class variables. The memory used by these variables will be allocated in the method area.

analysis

The process by which a virtual machine replaces a symbolic reference in a constant pool with a direct reference.
The parsing action mainly refers to class or interface, field, class method, interface method, method type, method handle and call point qualifier

initialization

The Java program code defined in the class is not really executed until the initialization stage. This stage is the process of executing the < clinit > () method.

Class loading timing

The virtual machine specification specifies that there are and only five situations in which classes must be "initialized" immediately (and loading, verification and preparation naturally need to start before that)

  1. When new, getstatic, putstatic or invokestatic bytecode instructions are encountered, if the class has not been initialized, its initialization needs to be triggered first. The corresponding scenarios are: instantiating an object with new, reading or setting the static field of a class (except the static field modified by final and the result has been put into the constant pool during compilation), and calling the static method of a class.
  2. When making reflection calls to a class, if the class has not been initialized, its initialization needs to be triggered first.
  3. When the parent class of the initialization class has not been initialized, the initialization of its parent class needs to be triggered first. (when an interface is initialized, it is not required that all its parent interfaces have completed initialization.)
  4. When the virtual machine starts, the user needs to specify a main class to execute (the class containing the main() method),
    The virtual opportunity initializes this main class first.
  5. When using the dynamic language support of JDK 1.7, if a Java lang.invoke. Final parsing result of methodhandle instance REF\_getStatic,REF\_putStatic,REF\_invokeStatic's method handle, and the class corresponding to this method handle has not been initialized, you need to trigger its initialization first.

be careful:

  1. Referencing a static field of a parent class through a subclass does not result in subclass initialization.
  2. Referencing a class through an array definition does not trigger the initialization of this class. MyClass[] cs = new MyClass[10];
  3. Constants are stored in the constant pool of the calling class at the compilation stage. In essence, they are not directly referenced to the class defining constants, so the initialization of the class defining constants will not be triggered.

Class loader

The code module that implements the action of "obtaining the binary byte stream describing this class through the fully qualified name of a class" in the class loading stage is called "class loader".

Put the binary data of the Class file into the method area, and then create a Java. Net file in the heap Lang. Class object, which encapsulates the data structure of the Class in the method area and provides developers with an interface to access the data structure in the method area.

Class uniqueness

For any class, the class loader that loads it and the class itself need to establish its uniqueness in the Java virtual machine.

Even if two classes come from the same Class file and are loaded by the same virtual machine, as long as the Class loaders that load them are different, the two classes are not equal.
The "equal" referred to here includes the return results of the equals() method, isAssignableFrom() method and isInstance() method of the Class object representing the Class, as well as the determination of the object's ownership relationship using the instanceof keyword

Parental entrustment mechanism

If a class loader receives a class loading request, it will not try to load the class itself, but delegate the request to the parent class loader to complete it. This is true for class loaders at each level, so all loading requests should eventually be transmitted to the top-level startup class loader, Only when the parent loader reports that it cannot complete the load request (it does not find the required class in its search scope), the child loader will try to load itself.

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // First, check if the class has already been loaded
//Add or not load this class from the cache first
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    if (parent != null) {
//Load from parent
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
//If you can't load it, load it yourself
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    c = findClass(name);
                }
            }
            return c;
    }
 
benefit
  • Avoid repeated loading. When the parent loader has loaded the class, it is not necessary for the child ClassLoader to load it again.
  • In consideration of security, prevent the core API library from being tampered with at will.

ClassLoader in Android

  • ClassLoader is an abstract class that defines the main functions of ClassLoader
  • BootClassLoader is a subclass of ClassLoader (note that it is not an internal class. Some materials say it is an internal class, which is wrong). It is used to load some classes required by the system Framework level and is the final parent of all classloaders on the Android platform
  • SecureClassLoader extends the ClassLoader class, adds the function of permission, and strengthens the security
  • URLClassLoader inherits secureclclassloader and is used to load classes and resources from jar files and folders through URI path. It is basically unavailable in Android
  • BaseDexClassLoader implements most of the functions of Android ClassLoader
  • PathClassLoader loads the classes of the application, and will load the dex file in the / data/app directory and the apk file or java file containing dex (some materials say that it will also load the system classes, which I didn't find, so I doubt it here)
  • DexClassLoader can load custom dex files and apk files or jar files containing dex, and supports loading from SD card. We will use it when we use plug-in technology
  • InMemoryDexClassLoader is used to load dex files in memory

Source code analysis of ClassLoader loading process

-> ClassLoader. Java class

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    synchronized(this.getClassLoadingLock(name)) {
        //First, check whether the class has been loaded. If it has been loaded, it will be returned directly
        Class<?> c = this.findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();

            try {
                if (this.parent != null) {
                   //Delegate to the parent loader to load ClassLoader parent;
                    c = this.parent.loadClass(name, false);
                } else {
                   //When executing to the top-level classloader, parent = null
                    c = this.findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException var10) {
            }

            if (c == null) {
                long t1 = System.nanoTime();
                c = this.findClass(name);
                PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                //If not found in the parent loader,
                PerfCounter.getFindClasses().increment();
            }
        }

        if (resolve) {
            this.resolveClass(c);
        }

        return c;
    }
}

Implemented by subclasses

protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

findClass method in BaseDexClassLoader class

protected Class<?> findClass(String name) throws ClassNotFoundException {
    List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
    // pathList is the DexPathList, where the code is stored.
    Class c = pathList.findClass(name, suppressedExceptions);
    if (c == null) {
        ClassNotFoundException cnfe = new ClassNotFoundException(
                "Didn't find class "" + name + "" on path: " + pathList);
        for (Throwable t : suppressedExceptions) {
            cnfe.addSuppressed(t);
        }
        throw cnfe;
    }
    return c;
}
public Class<?> findClass(String name, List<Throwable> suppressed) {
    for (Element element : dexElements) {
        Class<?> clazz = element.findClass(name, definingContext, suppressed);
        if (clazz != null) {
            return clazz;
        }
    }

    if (dexElementsSuppressedExceptions != null) {
        suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
    }
    return null;
}
public Class<?> findClass(String name, ClassLoader definingContext,
        List<Throwable> suppressed) {
    return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed) : null;
}
public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
    return defineClass(name, loader, mCookie, this, suppressed);
}

private static Class defineClass(String name, ClassLoader loader, Object cookie,
                                 DexFile dexFile, List<Throwable> suppressed) {
    Class result = null;
    try {
        result = defineClassNative(name, loader, cookie, dexFile);
    } catch (NoClassDefFoundError e) {
        if (suppressed != null) {
            suppressed.add(e);
        }
    } catch (ClassNotFoundException e) {
        if (suppressed != null) {
            suppressed.add(e);
        }
    }
    return result;
}

// Call Native layer code
private static native Class defineClassNative(String name, ClassLoader loader, Object cookie, DexFile dexFile)

This article is transferred from https://juejin.cn/post/7038476576366788621 , in case of infringement, please contact to delete.

Topics: Android