Before talking about classloaders and parental delegation models, let’s take a look at the loading process of Class Class files. To ensure implementation language independence, the JAVA Virtual machine only ties the virtual machine to a specific binary file format called “Class file” bytecodes, rather than binding it to the implementation language.
Class loading process
The Class lifecycle starts when it is loaded into vm memory and ends when it is unloaded. 2, Loading, Verification, Preparation, Resolution, Initialization, Using and Unloading are two phases. The loading process is as follows:
Loading stage
What does the loading phase do? The process is shown below. Where the fully qualified name of a Class is a symbolic reference to a hexadecimal value in the constant pool that is represented in the Class file (automatically generated by the JAVA compiler). The Class file format has its own set of specifications, such as bytes 1-4 for magic numbers, bytes 5-6 for minor versions, bytes 7-8 for major versions, and so on.
In plain English, the virtual machine doesn’t care where our “specific binary stream” comes from, whether it’s locally loaded or downloaded from the web. All the virtual machine has to do is write the binary stream to its own memory and generate the corresponding Class object (not in the heap). At this stage, we can control how the binary stream is fetched through our custom classloader.
Validation phase
In the verification phase, the virtual machine does not care about the binary source during the loading phase, so there may be security risks affecting the normal operation of the virtual machine. So the virtual machine is very important for the validation of this binary stream. The verification methods include but are not limited to:
Preparation stage
Preparation Phase This phase formally allocates memory for class variables and sets their initial values. Class variables are static variables that are allocated in the method area, not object variables that are allocated in the heap. It is also important to note that final constants are already assigned at this stage. As follows:
public static int SIZE = 10; // Initialize value == 0
public static final int SIZE = 10; // Initialize the value == 10
Copy the code
Parsing stage
The parsing phase is the process of replacing symbolic references in the constant pool with direct references. Symbolic references are specific literals specified by the Class file format standard, while direct references are Pointers, memory references, and so on
Initialization phase
In the initialization phase, it’s time to actually execute our bytecode program. The class initialization phase is the execution of the class construction < Clinit >() method within the virtual machine. Note that this class constructor is not generated internally by the virtual machine, but is generated automatically by our compiler, which automatically collects all class variable assignments and combines them with statements in the static{} block (see below).
Note that the class variable assignment is static and has an assignment. If there is no assignment, then the method area initialization in the preparation phase is complete. Why static{}? Static {} is a special “static clause” composed of multiple static initializers, like any other static initializer. This is why static {} is executed only once and before the object constructor. The following code:
public class Tested {
public static int T;
// public static int V; // No assignment, no reinitialization in class construction
public int c = 1; // Not in class construction
static {
T = 10; }}Copy the code
Also, the order in which the editor collects the class variables, that is, the order in which the virtual machine is executed during this initialization phase, is the order in which the variables are defined in the class statements, as shown above: statement 2: T comes before statement 6: T, which are two separate statements. Other characteristics of the class construct < Clinit > are as follows:
Compile-time < Clinit >
Let’s trace the process back to compile time, using the Tested class code we just came up with. Javap-c /Tested. Class (note: /.. /Tested absolute path), get Class file:
public class com.tencent.lo.Tested {
public static int T;
public int c;
public com.tencent.lo.Tested();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_1
6: putfield #2 // Field c:I
9: return
static {};
Code:
0: bipush 10
2: putstatic #3 // Field T:I
5: return
}
Copy the code
The < clinit > Class constructor is visible in the Class file. The < clinit > Class constructor is visible in the Class file. The < clinit > Class constructor is visible in the Class file. Static {} Let’s take a look at the Constants interface of the OpenJDK source code, which defines Constants used in the compiler. It’s an automatically generated class.
public interface Constants extends RuntimeConstants {
public static final boolean tracing = true;
Identifier idClassInit = Identifier.lookup("<clinit>");
Identifier idInit = Identifier.lookup("<init>");
}
Copy the code
In the MemberDefinition class, determine if it is a class constructor character:
public final boolean isInitializer(a) {
return getName().equals(idClassInit); / / class structure
}
public final boolean isConstructor(a) {
return getName().equals(idInit); // Object construction
}
Copy the code
In MemberDefinition’s toString() method, we can see that when a class is constructed, it outputs a specific character, not a canonical string like an object construct.
public String toString(a) {
Identifier name = getClassDefinition().getName();
if (isInitializer()) { / / class structure
return isStatic() ? "static {}" : "instance {}";
} else if (isConstructor()) { // Object construction
StringBuffer buf = new StringBuffer();
buf.append(name);
buf.append('(');
Type argTypes[] = getType().getArgumentTypes();
for (int i = 0 ; i < argTypes.length ; i++) {
if (i > 0) {
buf.append(', ');
}
buf.append(argTypes[i].toString());
}
buf.append(') ');
return buf.toString();
} else if (isInnerClass()) {
return getInnerClass().toString();
}
return type.typeString(getName().toString());
}
Copy the code
Class loader
The virtual machine implements the class-loading action of “get the binary stream describing this class by a fully qualified name” externally so that the developer can decide how to get the required class files. The code module that implements this action is called the classloader. For any class, it only makes sense to compare whether the two are the same if the class loader is the same. Otherwise, the same file under different loaders will still look different to the virtual machine as two separate classes. We can classify class loaders into three categories “:
Parents delegate
The so-called parental delegation model is: “If a class loader receives a request for a class load, it does not attempt to load the class itself. Instead, it delegates the loading to the parent class loader. This is true at each level of the loader, so all load requests are sent to the top level of the starting class loader. Only when the parent loader reports that it is unable to complete the load request (its search scope does not find the desired class, because the launcher and extension classloaders described above can only load libraries under a specific directory or specified by the -x argument) will the subclass attempt to load itself. Note that the parent class is only used to describe a hierarchy, which is not directly inherited, but is composed to duplicate the loader of the parent class.
“The advantage of parental delegation is that the loader also has a hierarchy of priorities. For example, java.lang.Object is stored in the rt.jar package under < JAVA_HOME>\lib, and any classloader that loads this class will eventually delegate to the topmost bootstrap classloader, thus ensuring that the Object class is the same class in all loader environments. Conversely, without the parental delegation model, if the user writes a java.lang.Object class and places it in the program’s ClassPath, the system will have multiple different Object classes.
Why? Because each loader is independent and does not delegate to the parent constructor, as mentioned above, it is independent even if the Class files are the same as long as the loaders are different.
If you write a java.lang.Object class in your project (of course, you can’t put it in the rt.jar library to replace the Object file of the same name, it doesn’t make sense to do so. If the virtual machine load verification passes, it just changes the source code), can we use a custom constructor to load this class? Theoretically, although both classes are java.lang.Object, these are different Class files for the virtual machine because of the different constructors, of course. But in practice? See the following code:
public void loadPathName(String classPath) throws ClassNotFoundException {
new ClassLoader() {
@Override
publicClass<? > loadClass(String name)throws ClassNotFoundException {
InputStream is = getClass().getResourceAsStream(name);
if (is == null)
return super.loadClass(name);
byte[] b;
try {
b = new byte[is.available()];
is.read(b);
} catch (Exception e) {
return super.loadClass(name);
}
return defineClass(name, b, 0, b.length);
}
}.loadClass(classPath);
}
Copy the code
The actual execution logic is the defineClass method. You can see that custom loaders cannot load system classes that begin with Java.
protected finalClass<? > defineClass(String name,byte[] b, int off, int len,
ProtectionDomain protectionDomain)
throwsClassFormatError { protectionDomain = preDefineClass(name, protectionDomain); ./ / a little
return c;
}
private ProtectionDomain preDefineClass(String name, ProtectionDomain pd) {
if(! checkName(name))throw new NoClassDefFoundError("IllegalName: " + name);
// You can see the system class here. Custom loaders cannot be loaded
if((name ! =null) && name.startsWith("java.")) {
throw new SecurityException
("Prohibited package name: " +
name.substring(0, name.lastIndexOf('. '))); }.../ / a little
return pd;
}
Copy the code
If you look directly at defineClass with AS, you will see that there is no concrete implementation inside defineClass, see the source code below. This doesn’t mean that android’s defineClass implementation is different from Java’s, as they are all ClassLoader classes that reference the java.lang package and the logic is definitely the same. The source code you see is different because of the difference between the SDK and the JAVA source package. The source code within the SDK is provided by Google for our convenience to develop view, not completely equal to the source code.
protected finalClass<? > defineClass(String name,byte[] b, int off, int len)
throws ClassFormatError
{
throw new UnsupportedOperationException("can't load this type of class file");
}
Copy the code
Well, that’s the end of this article, and that should suffice for the class loading process. If this article is useful to you, give it a thumbs up. Everyone’s affirmation is also the motivation for Dumb I to keep writing.
1. Zhou Zhiming, Understanding JAVA Virtual Machines in Depth: China Machine Press