This is the third day of my participation in the August More text Challenge. For details, see: August More Text Challenge

Class loader

ClassLoader: it reads binary data from the.class file of a class into memory, places it in the method area of the runtime data area, and converts it into an instance of a Java.lang.class object corresponding to the target class. The JVM specification allows classloaders to pre-load a class when it is going to be used.

Class loading mechanism The process of class loading

When a Java program runs, the JDK actually executes a Java command, specifies the full class name containing the main method, and a classpath as the entry point to the program, and then looks for and loads the class based on its fully qualified name by looking in the system class and the specified file classpath. If it is in the root directory of the class file, check whether there are corresponding subdirectories and class files. If the current path is a JAR file, first decompress the file, and then go to the directory to check whether there are corresponding classes. In this process, the Class responsible for completing the operation is the ClassLoader. The input is the fully qualified Class name, and the output is the corresponding Class object. There are several types of classloaders:

BootstrapClassLoader: an internal implementation of the Java VIRTUAL machine. This class is responsible for loading the Java base classes, that is, all types in the JAVA_HOME/lib directory or in the path specified by -xBootclasspath. Classes such as String, Array, and rt.jar in the lib folder directory of the JDK folder

ExtClassLoader: Inherits from the Abstract ClassLoader class. The default implementation class is the ExtClassLoader class in the sun.misc.Launcher package

ExtClassLoader: Loads all types in the JAVA_HOME/lib/ext directory.

AppClassLoader: The default implementation class of the ClassLoader is the AppClassLoader class in the sun.misc.Launcher package. By default, this loader is responsible for loading the application classes, including its own implementation classes and imported third-party class libraries. That is, it will load all specified classes in the entire Java program directory.

Class loading process

A complete class loading process must go through three steps: load, connect, and initialize

loading

The MAIN purpose of the JVM at this stage is to convert the bytecode from a different source (be it a class file, a JAR package, or even a network) into a binary byte stream to load into memory and generate a Java.lang.class object that represents the class.

Compared to the other phases of class loading, the load phase (specifically, the action of obtaining the binary byte stream of a class during the load phase) is the most controllable because it can be done using either system-provided class loaders or custom class loaders. After the loading phase is complete, the binary stream of bytes outside the virtual machine is stored in the method area in the format required by the virtual machine, and an object of Class Java.lang.class is created in the Java heap to access the data in the method area.

validation

The JVM validates the binary byte stream at this stage, and only those that conform to the JVM bytecode specification will be executed correctly by the JVM. This phase is an important barrier to JVM security, and the following are some major checks. Ensure that the binary byte stream format is as expected, that all methods comply with access control keyword restrictions, that method calls have the correct number and type of arguments, that variables are properly initialized before use, and that variables are assigned values of the appropriate type.

To prepare

Class variables (also called static variables, modified by the static keyword) are allocated and initialized by the JVM at this stage (corresponding to the default initial value of the data type, such as 0, 0L, null, false, etc.).

In other words, suppose you have a piece of code like this:

Public String test1 = "1"; Public static String test2 = "1 "; Public static final String test3 = "test3 "; Test1 will not be allocated memory, while test2 will. The initial value for test2, however, is not "king 2" but null.Copy the code

Note that static final modified variables are called constants, unlike class variables. The constant does not change once it is assigned, so test3 will have a value of “Nugget 3” instead of null in the prepare phase.

parsing

This phase converts symbolic references in the constant pool to direct references.

A symbolic reference is a set of symbols (any form of literal that can be used to locate the target unambiguously) to describe the referenced target. At compile time, the Java class does not know the actual address of the referenced class, so only symbolic references can be used instead. A direct reference resolves the symbolic reference to find the actual memory address of the reference.

Initialize the

This phase is the final step in the class loading process. During the preparation phase, the class variable has been assigned the default initial value, and during initialization, the class variable will be assigned the value the code expects to assign. In other words, the initialization phase is the process of executing the class constructor methods.

For example

String juejin = new String(" gold ");Copy the code

The above code uses the new keyword to instantiate a String object. In this case, the constructor of the String class is called to instantiate the juejin.

Parent delegation model

If a classloader receives a request to load a class, it will first delegate the request to the upper-level loader, which in turn will delegate to the upper-level loader, all the way to the top-level classloader. If the upper loader is unable to finish loading the class, the current class loader will attempt to load the class itself. One obvious benefit of using the parent delegate model is that Java classes, along with their class loaders, have a hierarchy of priorities, which is important for the stable operation of Java programs.

The implication is that if two classes have different loaders, they must not be equal even if they come from the same bytecode file — the parent delegate model guarantees that the same class will eventually be loaded by a particular class loader. This prevents multiple copies of the same bytecode from appearing in memory and ensures the safe and stable operation of Java programs.

Custom class loaders

    

protected Class<? > loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<? > c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); 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. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; }}Copy the code

Check whether the class has been loaded. If so, you do not need to reload it.

2) If it is not loaded, it is loaded by the parent class loading (recursion in turn) or the bootstrap class loader.

3) If not, call the findClass method of this loader;

The classloader loads through the parent class first. If the parent class is not found, the classloader loads. The resolve identifier is the same as the initialize parameter for forName. This identifier determines whether to initialize a block of code after the Class has been loaded, but as you can see from the above method, the default value is false. That is, only the Class Class is loaded and the initialization block of the Class is not called. Test code is as follows:

The loaded classes

public class Juejin { public Juejin() { System.out.println("Juejin:" + getClass().getClassLoader()); System.out.println("Juejin Parent:" + getClass().getClassLoader().getParent()); } public String print() { System.out.println("Juejin:print()"); return "JuejinPrint"; }}Copy the code

TClassLoader:

class TClassLoader extends ClassLoader { private String classPath; public TClassLoader(String classPath) { this.classPath = classPath; } @Override protected Class<? > findClass(String name) throws ClassNotFoundException { try { byte[] data = loadByte(name); return defineClass(name, data, 0, data.length); } catch (Exception e) { e.printStackTrace(); throw new ClassNotFoundException(); Private byte[] loadByte(String name) throws Exception {name = name.replaceAll("\\.", "/"); FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class"); int len = fis.available(); byte[] data = new byte[len]; fis.read(data); fis.close(); // Data = desinstance.decode ("1234567890qwertyuiopasdf".getBytes(), data); return data; }}Copy the code
@Test
public void testClassLoader() throws Exception {
    TClassLoader myClassLoader = new HClassLoader("D:/demo/a");
    Class clazz = myClassLoader.loadClass("com.Demo.Juejin");
    Object o = clazz.newInstance();
    Method print = clazz.getDeclaredMethod("print", null);
    print.invoke(o, null);
}
Copy the code