[toc]
I. The life cycle of a class
The entire class life cycle is divided into two phases: load and use.
A physical class file is loaded into memory via a binary stream. The JVM checks whether the class file is valid. The compliant class file is parsed into a Klass structure for storing in the method area, and a Klass mirror object class is generated on the Java side.
When we instantiate an object via new or reflection, the object’s object header holds a pointer to Klass in the heap. This allows us to know the methods and properties defined in the class using the Klass structure.
1.1. Class loading
Java/Kotlin/Groovy code coded by the IDE is compiled to generate a.class file, which is a structured binary data stream.
Using the classLoader, we can load the class binary data stream into memory, parse each field according to the DSL specification of the class, generate the klass structure on the c++ side, and store klass in the method area (metadata area) of the heap. Generate a Java-side Class object and place it in the heap.
Here is more around the mouth, a simple understanding can be compared like this:
Class —–> Persion object
Klass ——–> Class object
When we create a new object, the object header contains the mark word, the Klass pointer, and the length of the array. The klass pointer points to the address of the Klass object in the method area. The core of the oop-Klass model is to separate the contents of Klass from the objects themselves and use Pointers to associate them so that every object of the same type does not need to store a copy of Klass information.
Once this is done, the class file is converted from the bytecode on the physical disk to the Klass object in the heap and stored in the method area.
1.2. Links to classes
Once the class is loaded and converted to Klass, it needs to enter the link phase before it can be used properly. The linking process consists of three phases:
- Check phase
- Preparation stage
- Parsing stage
Check phase
Format check – Magic (cafebaby) check, version check, length check semantic check – Abstract method is implemented, is there a parent class and other bytecode check – operand address is correct, jump instruction address is correct symbol reference check – symbol reference is present (# XXX corresponding constant is present)
Preparation stage
Allocates memory for static variables of the class and initializes default values
Parsing stage
Change symbolic references to interfaces, classes, methods, and properties to direct references
Symbolic reference: typically represented as # XXX, this string looks up the actual content from the constant pool.
Direct reference: memory address or offset. For class methods, a direct reference to a class variable is a pointer to the method area; For instance methods, a direct reference to an instance variable is an offset from the instance’s header pointer to the location of the instance variable
1.3. Class initialization
Execute the built-in cinit methods, assign initial values to variables, and put some static wrapped methods in cinit.
1.4. Use of classes
Depending on whether or not the CINit method is implemented, the use of classes is either active or passive. Active use will execute the cinit methods of the class, while passive use will not.
Active use scenarios
- new XXX()
- Use static fields and methods of the class
- class.forName(“xxx”)
- xxx.newInstantce()
Passive use scenario
- Classes defined by data
- ClassLoader.loadClass(“xxx”)
- Use constants in the class. Constants are processed at compile time, so cinIT is not triggered
- Referencing a class through an array definition does not trigger initialization
Ii. Types of ClassLoader
2.1. The Bootstrap this
Load the JAR package in Java_Home/lib
2.2. The Extension of this
Load the JAR package in Java_Home/lib/ext/
2.3. The Application of this
Is responsible for loading jar packages in the java-classpath specified path
2.4. Customize ClassLoader
You can specify a jar package in a directory of your own
3. This android
3.1. BootClassLoader
BootClassLoader is an internal class of ClassLoader and cannot be used directly
Path: Java/lang/this
class BootClassLoader extends ClassLoader { private static BootClassLoader instance; @FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED") public static synchronized BootClassLoader getInstance() { if (instance == null) { instance = new BootClassLoader(); } return instance; } public BootClassLoader() { super(null); } @Override protected Class<? > findClass(String name) throws ClassNotFoundException { return Class.classForName(name, false, null); } @Override protected Class<? > loadClass(String className, boolean resolve) throws ClassNotFoundException { Class<? > clazz = findLoadedClass(className); if (clazz == null) { clazz = findClass(className); } return clazz; }}Copy the code
Key analysis:
- BootClassLoader is an internal class of ClassLoader. It cannot be used directly. It inherits from ClassLoader.
- Its loadClass is the core method for all classes to be loaded. It looks for classes that have already been loaded, or if not, it looks for class.classForname (see section 3.2)
3.2 Class. ClassForName
Path: Java/lang/Class. Java
@FastNative static native Class<? > classForName(String className, boolean shouldInitialize, ClassLoader classLoader) throws ClassNotFoundException;Copy the code
This is a native method that, according to JNI rules, calls the Class_classForName method under java_lang_Class
3.3 java_lang_Class.Class_classForName
Path: art/runtime/Native. Cc
static jclass Class_classForName(JNIEnv* env, jclass, jstring javaName, jboolean initialize, jobject javaLoader) { ScopedFastNativeObjectAccess soa(env); ScopedUtfChars name(env, javaName); if (name.c_str() == nullptr) { return nullptr; } // We need to validate and convert the name (from x.y.z to x/y/z). This // is especially handy for array types, since we want to avoid // auto-generating bogus array classes. if (! IsValidBinaryClassName(name.c_str())) { soa.Self()->ThrowNewExceptionF("Ljava/lang/ClassNotFoundException;" , "Invalid name: %s", name.c_str()); return nullptr; } std::string descriptor(DotToDescriptor(name.c_str())); StackHandleScope<2> hs(soa.Self()); Handle<mirror::ClassLoader> class_loader( hs.NewHandle(soa.Decode<mirror::ClassLoader>(javaLoader))); ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); Handle<mirror::Class> c( hs.NewHandle(class_linker->FindClass(soa.Self(), descriptor.c_str(), class_loader))); if (c == nullptr) { ScopedLocalRef<jthrowable> cause(env, env->ExceptionOccurred()); env->ExceptionClear(); jthrowable cnfe = reinterpret_cast<jthrowable>( env->NewObject(WellKnownClasses::java_lang_ClassNotFoundException, WellKnownClasses::java_lang_ClassNotFoundException_init, javaName, cause.get())); if (cnfe ! = nullptr) { // Make sure allocation didn't fail with an OOME. env->Throw(cnfe); } return nullptr; } if (initialize) { class_linker->EnsureInitialized(soa.Self(), c, true, true); } return soa.AddLocalReference<jclass>(c.Get()); }Copy the code
FindClass information by ClassLinker->FindClass
3.4. ClassLinker FindClass
Path: art/runtime/class_linker. Cc
This method is very long, so I’ll keep only the most important parts of it
ObjPtr<mirror::Class> ClassLinker::FindClass(Thread* self, const char* descriptor, Handle<mirror::ClassLoader> class_loader) { .... const size_t hash = ComputeModifiedUtf8Hash(descriptor); // Find the class in the loaded classes table. ObjPtr<mirror::Class> klass = LookupClass(self, descriptor, hash, class_loader.Get()); if (klass ! = nullptr) { return EnsureResolved(self, descriptor, klass); } // Class is not yet loaded. if (descriptor[0] ! = '[' && class_loader == nullptr) { // Non-array class and the boot class loader, search the boot class path. ClassPathEntry pair = FindInClassPath(descriptor, hash, boot_class_path_); if (pair.second ! = nullptr) { return DefineClass(self, descriptor, hash, ScopedNullHandle<mirror::ClassLoader>(), *pair.first, *pair.second); } else { // The boot class loader is searched ahead of the application class loader, failures are // expected and will be wrapped in a ClassNotFoundException. Use the pre-allocated error to // trigger the chaining with a proper stack trace. ObjPtr<mirror::Throwable> pre_allocated = Runtime::Current()->GetPreAllocatedNoClassDefFoundError(); self->SetException(pre_allocated); return nullptr; }}}...Copy the code
This class is very long, and the highlights are as follows
ObjPtr<mirror::Class> klass = LookupClass(self, descriptor, hash, class_loader.Get()); if (klass ! = nullptr) { return EnsureResolved(self, descriptor, klass); }Copy the code
Find klass in the method area, generate a class object and return it
ClassPathEntry pair = FindInClassPath(descriptor, hash, boot_class_path_); if (pair.second ! = nullptr) { return DefineClass(self, descriptor, hash, ScopedNullHandle<mirror::ClassLoader>(), *pair.first, *pair.second); }Copy the code
If not, the bytecode is loaded from dex and then pushed into the method area via DefineClass to generate klass and returned, which can be queried by those interested.
3.5 this
public abstract class ClassLoader { static private class SystemClassLoader { public static ClassLoader loader = ClassLoader.createSystemClassLoader(); } private static ClassLoader createSystemClassLoader() { String classPath = System.getProperty("java.class.path", "."); String librarySearchPath = System.getProperty("java.library.path", ""); return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance()); } protected ClassLoader() { this(checkCreateClassLoader(), getSystemClassLoader()); } protected Class<? > loadClass(String name, boolean resolve) throws ClassNotFoundException { // First, check if the class has already been loaded Class<? > c = findLoadedClass(name); if (c == null) { try { if (parent ! = null) { 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 (c == null) { // If still not found, then invoke findClass in order // to find the class. c = findClass(name); } } return c; }... } class BootClassLoader extends ClassLoader{.... }Copy the code
In the ClassLoader no-argument constructor, the default parent ClassLoader is a PathClassLoader, and the default parent passed by the PathClassLoader is BootClassLoader.
So in this calls loadClass, if already loaded, the direct return, if there is no load, entering a PathClassLoader. LoadClass (), because PathClassLoader not rewrite loadClass, It then goes to the loadClass of the BootClassLoader and finally gets the klass from the classLinker.
3.6 PathClassLoader
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
@SystemApi(client = MODULE_LIBRARIES)
@libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
public PathClassLoader(
@NonNull String dexPath, @Nullable String librarySearchPath, @Nullable ClassLoader parent,
@Nullable ClassLoader[] sharedLibraryLoaders) {
super(dexPath, librarySearchPath, parent, sharedLibraryLoaders);
}
}
Copy the code
PathClassLoader simply overrides the BaseDexClassLoader constructor and passes parent (BootClassLoader) to BaseDexClassLoader
3.7. BaseDexClassLoader
public class BaseDexClassLoader extends ClassLoader { public BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent) { this(dexPath, librarySearchPath, parent, null, false); } public BaseDexClassLoader(String dexPath, String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders, boolean isTrusted) { super(parent); // Setup shared libraries before creating the path list. ART relies on the class loader // hierarchy being finalized before loading dex files. this.sharedLibraryLoaders = sharedLibraryLoaders == null ? null : Arrays.copyOf(sharedLibraryLoaders, sharedLibraryLoaders.length); this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted); // Run background verification after having set 'pathList'. this.pathList.maybeRunBackgroundVerification(this); reportClassLoaderChain(); }}Copy the code
Key notes:
- DexPath: ApK/JAR list containing target classes or resources
- OptimizedDirectory: directory where dex files exist after optimization, deprecated after API 26
- LibrarySearchPath: native library path list; Adopt: split
- ClassLoader: the ClassLoader for the parent class.
- When classLoader loads dex, jar/apk/dex will be converted to dexFile. MakeDexElements will be converted to Element array. The class is returned in order from the Element array, so the class in the dex that always preceded the element array will be returned first. We can also hot-fix by inserting the modified dex ourselves.
3.8. DexClassLoader
public class DexClassLoader extends BaseDexClassLoader { public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) { super(dexPath, null, librarySearchPath, parent); }}Copy the code
DexClassLoader also simply overwrites the BaseDexClassLoader constructor. The core difference between DexClassLoader and PathClassLoader is that it provides optimizedDirectory as an external parameter.
- Before API26, DexClassLoader specified the location of the odex output through optimizedDirectory. PathClassLoader used the default odex directory /data/dalvik-cache/[email protected]
- OptimizedDirectory is deprecated after API26, so PathClassLoader and DexClassLoader work exactly the same after API26.