The introduction
As we all know, Java programs and apps are made up of several.class files. Instead of loading all the.class files at once, the program is loaded dynamically (on demand) according to the needs of the program through Java’s classloading mechanism. To explore Java’s classloading mechanism further, you have to refer to the ClassLoader.
The definition and function of a ClassLoader
A ClassLoader is essentially an abstract class. It acts like a container that stores the class paths of different class files. In The Android system, mainly responsible for:
1. Load dex file, here also involves the 65536 problem most familiar to Android developers, not to mention.
2. Convert classes from bytecode form into Java.lang. Class objects and load them into the JVM for identification, analysis, and execution.
In Android, whether plug-ins, bytecode encryption, hot fixes, or skin peening, ClassLoader is essential. Therefore, it’s worth taking a closer look at how ClassLoader works in the Android system.
How ClassLoader works (parent delegate)
A ClassLoader, before attempting to load a class (ClassLoaderB trying to load classA), will delegate its parent ClassLoader (ClassLoaderC) to try to load the class, and the parent ClassLoader will give it to its parent ClassLoader before attempting to load the class. And so on (like a tongue twister).
The whole process is shown in the figure below: classA is our target class, and in general, ClassLoaderD is passed through the classloading mechanism to the top-level ClassLoaderD processing, that is, ClassLoaderD is the classloader that actually does the loading.
If a ClassLoader successfully loads a Class, it will cache the resulting instance of the Java.lang. Class Class. If it is not completed by the top-level classloader (ClassLoaderD), it is returned to the subclass loader (ClassLoaderC) and passed down the level.
This classification
There are two types of classloaders: system class loaders and custom class loaders
The system class loaders are provided by default by Java and can be divided into three categories:
Bootstrap ClassLoader:
Responsible for loading Java base classes, mainly rt.jar, resources. Jar, charsets. Jar and class in %JRE_HOME/lib/ directory.
Extensions ClassLoader
Responsible for loading Java extension classes, mainly in %JRE_HOME/lib/ext directory jar and class, etc.
Application ClassLoader:
Responsible for loading all classes in the clasSPath of the current Java application.
Status, in fact, is the priority of the loading class: the higher the status, the more work. In other words, the above diagram can actually be redrawn like this:
Just as each of the three system class loaders is responsible for a specific load path, each does its own job. Each corresponding class object has a ClassLoader field inside it to mark which loader it was loaded by.
So how does each ClassLoader find its own parent ClassLoader? The BootStrap ClassLoader is special because it is a Navite class. So the getParent method of the Extension ClassLoader returns NULL.
And how did the father-son relationship work? It’s simple. There’s no why. Because it’s already fixed in the code.
Our custom ClassLoader is usually called after the App ClassLoader is unable to load a class, so it is between the App ClassLoader and classA. This makes sense, given how well the system is built in.
A ClassLoader is loaded by its parent class loader. If the parent class loader has been loaded, it is loaded by its parent class loader. If the parent class loader has not been loaded, it is loaded by its parent class loader. If at the end of the day none of the families can load, a ClassNotFoundException will be reported. Sorry, that’s more than one sentence
The custom this
A custom ClassLoader is needed for specific needs that require specific loading behavior.
How to customize a ClassLoader:
1. Java inheritance. Lang. This
2. Override the findClass method of the parent class
Why only override the findClass method, do you look down on me loadClass? No, listen to me. The main reasons are as follows:
1. The JDK implements the ClassLoader search algorithm in the loadClass method. If the loadClass method cannot find the class, the findClass method will be called to search for the class. With the template code provided, we just need to write our own custom code in findClass.
2. In fact, findClass defaults to throwing exceptions if not overridden, so we should override them. As shown in the figure:
Why parental delegation
We all know that some of the quirks of computer systems are designed to save time or space or to be safe, and using parent delegation is no exception. The previous mention of custom Classloaders has not been mentioned, but it involves the security aspects and resource savings of the parent delegation mode.
There are two benefits to parent delegation: On the one hand, when the parent class loader has already loaded the class, the child class loader can “fetch” and avoid reloading the resource. On the other hand, it prevents developers from writing their own java.lang.String or Java.lang. Map or whatever to replace the types already defined by the system in Java.
Take a chestnut
Imagine if we wrote a rogue java.lang.String to replace the java.lang.String in the system, and changed it in a few places to achieve an unknown purpose. Let’s examine how the custom evil java.lang.String will be loaded: It is first loaded from the App ClassLoader, then handed to the Extensions ClassLoader based on parent delegation, and then to the Bootstrap ClassLoader. The whole process is:
App ClassLoader -> Extensions ClassLoade -> Bootstrap ClassLoader
When we introduced the ClassLoader class above, we mentioned classes in jar packages in different directories for which different system classloaders are responsible. Bootstrap is responsible for loading the java.lang.* package The ClassLoader will find a java.lang.String with the same name as the one we defined in $JAVA_HOME/jre/lib/rt.jar and load the class without a problem. Then you realize that your evil Java.lang. String has been ignored, and the first phase of the evil code invasion plan has failed.
There is also a hidden level in classA and App ClassLoader. By forcing my evil java.lang.String into a custom ClassLoader and not reporting it to the parent ClassLoader, we’re done. If you search the Internet, you’ll find someone else with the same idea:
It looks like half of the battle is over. The good guy operates like a tiger. Look at the 0-5 console. ** This is not actually feasible. ** Looking at the SecurityException in the log, you get lost in thought and decide to start with the source code, only to realize that the source code already has a lot of nets:
Originally we wanted to implement a custom ClassLoader that must inherit from the ClassLoader, and we already limited the package names in the loadClass() method. In short, a custom classloader cannot load a class whose package name begins with “Java.” This can be awkward, and a SecurityException can help.
Determine whether the classes are the same
When determining whether two classes are the same, the JVM must not only determine whether the two classes have the same name, but also whether they were loaded by the same class loader instance. The JVM considers two classes the same only if both are satisfied. Even if two classes are the same class bytecode, the JVM will treat them as two different classes if they are loaded by two different ClassLoader instances. Different class loaders allow classes with the same name to exist in the JVM, which is widely used in many frameworks.
Figure code version Android11.0, the English part of the annotations for the source annotations.
Technology must be landed to serve the business, otherwise it is on paper. Let’s take a look at how ClassLoader handles this in real life.
Use of the parent delegate model in hotfixes
As we all know, classes are loaded by the findClass method in DexPathList, and the application of the parent delegate model in hotfix technology demonstrates this principle.
Simply put, the fixed class with the same name is placed at the first place in the Element array through dex piling. In the process of class loading, the class with the same name will be loaded first, so as to replace the faulty class and achieve the effect of hot fix.