preface

This article will introduce the process and principle of Java class loading by shallow and deep, and further source analysis of the class loader to complete a custom class loader.

The body of the

(a). What is a class loader

Classloaders, in short, are tools for the process of converting bytecode information in.class files into concrete Java.lang.class objects.

Specific process:

  1. In the actual class loading process,JVMWill put all of the.classIn the bytecode fileBinary dataRead into memory to import the runtime data areaMethods areaIn the.
  2. When a class is first loaded actively or passively, the classloader performs the classloading process-load, connect (validate, prepare, parse), initialize for that class.
  3. If the class loads successfully,Heap memoryA new one will be created inClassObject,ClassObject encapsulates the class inMethods areaWithin theThe data structure.

Class object creation process:

(ii) the process of class loading

The class loading process is divided into three steps (five stages) : load -> connect (validate, prepare, parse) -> initialize.

The four phases of load, validation, preparation, and initialization occur in a certain order, while the parsing phase can occur after the initialization phase, also known as dynamic binding or late binding.

Class loading process description:

1. The load

Load: The process of finding and loading the binary data of a class.

Loading process description:

  1. Through the classFully qualified namepositioning.classFile and get itBinary byte stream.
  2. Converts the static storage structure represented by the byte stream into the runtime data structure of the method area.
  3. inJavaThe heapGenerates a class of this typejava.lang.ClassObject as the data in the method areaAccess to the entry.

2. The connection

Connection: including verification, preparation, parsing three steps.

A) validation

Validation: To ensure the correctness of the classes being loaded. Validation is the first step in the connection phase to ensure that the information in the Class byte stream meets the requirements of the virtual machine.

Specific verification form:

  1. File format validation: verifies byte stream complianceClassSpecification of file format; For example, whether to use0xCAFEBABEStart, major and minor versions are within the scope of the current VIRTUAL machine, and constants in the constant pool are of unsupported types.
  2. Metadata validation: Performs semantic analysis of the information described by bytecode (note: contrastjavacSemantic analysis at compile time) to ensure that the information described conforms to the Requirements of the Java language specification; For example: does this class have a parent, exceptjava.lang.ObjectOutside.
  3. Bytecode validation: Determine that program semantics are legitimate and logical through data flow and control flow analysis.
  4. Symbol reference validation: Ensures that the parse action is performed correctly.

B) preparation

Preparation: Allocates memory for static variables of the class and initializes them to default values. The preparation process usually allocates a structure to store the class information. This structure contains the member variables, methods, and interface information defined in the class.

Specific behavior:

  1. At this time, the memory allocation only includesClass variables(static), but notThe instance variables.The instance variablesWill be in the objectinstantiationIs allocated in a block along with the objectJavaThe heapIn the.
  2. That’s set up hereThe initial valueUsually it’s a data typeThe default value is zero(e.g.,0,0L,null,falseEtc.) instead of being inJavaIn the code areExplicit assignment.

C) parsing

Parse: Convert symbolic references in a class to direct references in the constant pool.

The resolution action is mainly for class or interface, field, class method, interface method, method type, method handle and call point qualifier.

3. The initialization

Initialization: Assign the correct initial value to a class static variable (note that this is distinguished from the join resolution process).

Initialization target

  1. Implements initialization of the initial value specified when declaring a class static variable;
  2. Implements initialization of an initial value set using a static code block.

Initialization steps

  1. If this class is not loaded or connected, load or connect this class first.
  2. If the immediate parent of this class has not already been initialized, its immediate parent is initialized first.
  3. If there are initialization statements in a class, the initialization statements are executed in sequence.

The timing of initialization

  1. Create an instance of the class (newKey words);
  2. java.lang.reflectMethods in packages (e.g.Class.forname (" XXX "));
  3. Access or assign to a static variable of a class;
  4. Access the static method of the calling class;
  5. When a subclass of a class is initialized, the parent class itself is initialized.
  6. programmaticStart the entrance, includingmainMethods (e.g.SpringBootEntry class).

(3). Class active and passive reference

Active reference

Active reference: During the class loading phase, only load and connect operations are performed, not initialization operations.

Several forms of unsolicited reference

  1. Create an instance of the class (newKey words);
  2. java.lang.reflectMethods in packages (e.g.Class.forname (" XXX "));
  3. Access or assign to a static variable of a class;
  4. Access the static method of the calling class;
  5. When a subclass of a class is initialized, the parent class itself is initialized.
  6. programmaticStart the entrance, includingmainMethods (e.g.SpringBootEntry class).

Actively reference the 1-main method in the initial class

Code examples:

public class OptimisticReference0 {
    static {
        System.out.println(OptimisticReference0.class.getSimpleName() + " is referred!");
    }

    public static void main(String[] args) { System.out.println(); }}Copy the code

Running results:

OptimisticReference0 is referred!

Active reference 2 – Creating a subclass triggers initialization of the parent class

Code examples:

public class OptimisticReference1 {
    public static class Parent {
        static {
            System.out.println(Parent.class.getSimpleName() + " is referred!"); }}public static class Child extends Parent {
        static {
            System.out.println(Child.class.getSimpleName() + " is referred!"); }}public static void main(String[] args) {
        newChild(); }}Copy the code

Running results:

Parent is referred! Child is referred!

Active reference 3 – Access a class static variable

Code examples:

public class OptimisticReference2 {
    public static class Child {
        protected static String name;
        static {
            System.out.println(Child.class.getSimpleName() + " is referred!");
            name = "Child"; }}public static void main(String[] args) { System.out.println(Child.name); }}Copy the code

Running results:

Child is referred! Child

Active reference 4 – Assigns a static variable of the class

Code examples:

public class OptimisticReference3 {
    public static class Child {
        protected static String name;
        static {
            System.out.println(Child.class.getSimpleName() + " is referred!"); }}public static void main(String[] args) {
        Child.name = "Child"; }}Copy the code

Running results:

Child is referred!

Active reference 5 – Use the reflection mechanism provided by the java.lang.Reflect package

Code examples:

public class OptimisticReference4 {
    public static void main(String[] args) throws ClassNotFoundException {
        Class.forName("org.ostenant.jdk8.learning.examples.reference.optimistic.Child"); }}Copy the code

Running results:

Child is referred!

Passive reference

Passive reference: During the class load phase, load, connect, and initialize operations are performed.

Several forms of passive quoting:

  1. A static field referenced by a subclass does not cause the subclass to be initialized.
  2. Defining an array reference to a class without assigning does not trigger initialization of the class;
  3. Accessing a constant defined by a class does not trigger initialization of the class.

Passive reference 1 – A subclass reference to a static field of its parent class does not result in subclass initialization

Code examples:

public class NegativeReference0 {
    public static class Parent {
        public static String name = "Parent";
        static {
            System.out.println(Parent.class.getSimpleName() + " is referred!"); }}public static class Child extends Parent {
        static {
            System.out.println(Child.class.getSimpleName() + " is referred!"); }}public static void main(String[] args) { System.out.println(Child.name); }}Copy the code

Running results:

Parent is referred! Parent

Passive reference 2 – Defining an array reference to a class without assigning a value does not trigger initialization of the class

Code examples:

public class NegativeReference1 {
    public static class Child {
        static {
            System.out.println(Child.class.getSimpleName() + " is referred!"); }}public static void main(String[] args) {
        Child[] childs = new Child[10]; }}Copy the code

Running results:

There is no output

Passive reference 3 – Accessing a constant defined by a class does not trigger initialization of the class

Sample code:

public class NegativeReference2 {
    public static class Child {
        public static final String name = "Child";
        static {
            System.out.println(Child.class.getSimpleName() + " is referred!"); }}public static void main(String[] args) { System.out.println(Child.name); }}Copy the code

Running results:

Child

(iv). Three kinds of loaders

Class loaders: Class loaders are responsible for the types (classes and interfaces) in the loader and are identified by unique names.

The class loader’s organizational structure

Class loader relationship

  1. Bootstrap ClassloaderIs in theJavaThis parameter is initialized after the VM starts.
  2. Bootstrap ClassloaderResponsible for loadingExtClassLoaderAnd will beExtClassLoaderParent loader set toBootstrap Classloader
  3. Bootstrap ClassloaderAfter loadingExtClassLoaderAfter that, it will loadAppClassLoaderAnd will beAppClassLoaderThe parent loader is specified asExtClassLoader.

The role of the class loader

Class Loader implementation Concrete implementation class Responsible for loading the target
Bootstrap Loader C++ By c + + implementation %JAVA_HOME%/jre/lib/rt.jarAs well as-XbootclasspathParameter specifies the path and the class library in
Extension ClassLoader Java sun.misc.Launcher$ExtClassLoader %JAVA_HOME%/jre/lib/extUnder the path andjava.ext.dirsClass libraries in the path specified by the system variable
Application ClassLoader Java sun.misc.Launcher$AppClassLoader ClasspathAs well as-classpath,-cpClass that specifies the location specified by the directory orjarDocumentation, it’s alsoJavaThe default class loader for the program

Class loader characteristics

  • Hierarchy: Class loaders in Java are organized into hierarchies with parent-child relationships. The Bootstrap class loader is the father of all loaders.
  • Proxy mode: Based on the hierarchy, the proxy of a class can be proxy between loaders. When a loader loads a class, it first checks to see if it has been loaded in the parent loader. If the superloader has already loaded the class, it will be used directly. Instead, the class loader requests that the class be loaded
  • Visibility limitation: A child loader can look up classes in the parent loader, but a parent loader cannot look up classes in the child loader.
  • Dismount not allowed: Class loaders can load a class but cannot unload it, but they can delete the current class loader and create a new class loader.

Class loader isolation issues

Each class loader has its own namespace to hold loaded classes. When a Class loader loads a Class, it detects that the Class has been loaded by searching for the Fully Qualified Class Name stored in the namespace.

JVM and Dalvik only recognize a class by ClassLoader id + PackageName + ClassName, so it is possible to have two classes with exactly the same PackageName and ClassName in a running program. And if the two classes are not loaded by the same ClassLoader, there is no way to force an instance of one class into another class, which is ClassLoader isolation.

To address class loader isolation, the JVM introduced parental delegation.

(V) Parent entrustment mechanism

Core idea: first, check whether the class is loaded from the bottom up; Second, you try to load classes from the top down.

Specific loading process

  1. whenAppClassLoaderLoads aclassInstead of trying to load the class itself, it first loads the class requestdelegatetoParent class loaderExtClassLoaderTo complete.
  2. whenExtClassLoaderLoads aclassIt doesn’t try to load the class itself in the first place, but instead loads the class requestdelegatetoBootStrapClassLoaderTo complete.
  3. ifBootStrapClassLoaderLoading failed (for example in%JAVA_HOME%/jre/libWas not found inclass), can useExtClassLoaderTo try to load;
  4. ifExtClassLoaderAlso failed to load, will be usedAppClassLoaderTo load, ifAppClassLoaderIf the load fails, an exception is reportedClassNotFoundException.

Source code analysis

ClassLoader.class

  1. loadClass(): by specifying the classFully qualified name, by the class loaderdetection,loading,createAnd returns the class’sjava.lang.ClassObject.

ClassLoader implements the parent delegate mechanism through the loadClass() method for dynamic loading of classes.

LoadClass () itself is a recursive upward call.

  • Check that the class is loaded from the bottom up

    1. Through the firstfindLoadedClass()Methods from the mostBottom class loaderStart checking to see if the class is loaded.
    2. If it has been loaded, theresolveParameter determines whether to executeThe connectionProcedure and returnClassObject.
    3. If it is not loaded, it passesparent.loadClass()Delegate the parent class loader to perform the same checking (no wiring by default).
    4. untilTop-level class loaders, i.e.,parentIs empty, byfindBootstrapClassOrNull()Method try toBootstrap ClassLoaderCheck the target class in.
  • Try to load the class from the top down

    1. If the target class is still not found, theBootstrap ClassLoaderStart, passfindClass()Method to try the correspondingThe class directoryGo down and load the target class.
    2. If the load is successful, use theresolveParameter determines whether to executeThe connectionProcedure and returnClassObject.
    3. If the load fails, the default value isSubclass loaderTry loading until mostBottom class loaderAlso failed to load and was eventually thrownClassNotFoundException.
  1. findLoadedClass()

Finds if the target class is already loaded in the current class loader’s cache. FindLoadedClass () actually calls the underlying native method findLoadedClass0().

  1. findBootstrapClassOrNull()

Look for the top Bootstrap class loader to see if the target class has been loaded. Similarly, findBootstrapClassOrNull() actually calls the underlying native method findBootstrapClass().

  1. findClass()

ClassLoader is the abstract class under the java.lang package and the base class for all class loaders except Bootstrap. FindClass () is the abstract method that ClassLoader provides for subclasses to load the target class.

Note: Bootstrap ClassLoader does not belong to the JVM hierarchy. It does not follow the loading rules of ClassLoader. Bootstrap ClassLoader does not have subclasses.

  1. defineClass()

DefineClass () is a method that ClassLoader provides to subclasses to convert the binary data of a.class file into a valid java.lang.class object.

(6). Dynamic loading of classes

Several ways to load classes

  • throughThe command lineStart byJVMInitial loading;
  • throughClass.forName()Method dynamic loading;
  • throughClassLoader.loadClass()Method is loaded dynamically.

Class.forname () and this loadClass ()

  • Class.forName(): take classes.classFile loading toJVMClass is interpreted while executing the classstaticStatic code block;
  • ClassLoader.loadClass(): just load the.class file intoJVMIs not executedstaticThe code blockContent, only innewInstanceTo execute.

(7). Object initialization

Object initialization order

Static variables/static code blocks -> plain code blocks -> constructors

  1. Parent static variables and static code blocks (declared first, executed first);
  2. Subclasses static variables and static code blocks (declared first);
  3. Parent ordinary member variables and ordinary code blocks (declared first, executed first);
  4. Constructor of the parent class;
  5. Subclass plain member variables and plain code blocks (declared first, executed first);
  6. The constructor of a subclass.

Object initialization example

Parent.java

Children.java

Tester.java

Test results:

The test results show that the JVM follows the initialization order of the above objects when creating them.

(8). Custom class loader

Write your own class loader

Now that we’ve seen how to implement a custom class loader in the source code analysis phase, let’s start writing down our own class loader.

Step 1: Define the target classes Parent. Java and Children. Java to be loaded.

Parent.java

package org.ostenant.jdk8.learning.examples.classloader.custom;

public class Parent {
    protected static String CLASS_NAME;
    protected static String CLASS_LOADER_NAME;
    protected String instanceID;

	// 1. Execute static variables and static code blocks first (only once during class loading)
    static {
        CLASS_NAME = Parent.class.getName();
        CLASS_LOADER_NAME = Parent.class.getClassLoader().toString();
        System.out.println("Step a: " + CLASS_NAME + " is loaded by " + CLASS_LOADER_NAME);
    }

    // 2. Then execute variables and normal code blocks (executed each time an instance is created)
    {
        instanceID = this.toString();
        System.out.println("Step c: Parent instance is created: " + CLASS_LOADER_NAME + "- >" + instanceID);
    }

    // 3. Then execute the constructor
    public Parent(a) {
        System.out.println("Step d: Parent instance:" + instanceID + ", constructor is invoked");
    }

    public void say(a) {
        System.out.println("My first class loader..."); }}Copy the code

Children.java

package org.ostenant.jdk8.learning.examples.classloader.custom;

public class Children extends Parent {
    static {
        CLASS_NAME = Children.class.getName();
        CLASS_LOADER_NAME = Children.class.getClassLoader().toString();
        System.out.println("Step b: " + CLASS_NAME + " is loaded by " + CLASS_LOADER_NAME);
    }

    {
        instanceID = this.toString();
        System.out.println("Step e: Children instance is created: " + CLASS_LOADER_NAME + "- >" + instanceID);
    }

    public Children(a) {
        System.out.println("Step f: Children instance:" + instanceID + ", constructor is invoked");
    }

    public void say(a) {
        System.out.println("My first class loader..."); }}Copy the code

Step 2: implement CustomClassLoader CustomClassLoader

CustomClassLoader.java

public class CustomClassLoader extends ClassLoader {
    private String classPath;

    public CustomClassLoader(String classPath) {
        this.classPath = classPath;
    }

    @Override
    protectedClass<? > findClass(String name)throwsClassNotFoundException { Class<? > c = findLoadedClass(name);/ / can be omitted
        if (c == null) {
            byte[] data = loadClassData(name);
            if (data == null) {
                throw new ClassNotFoundException();
            }
            return defineClass(name, data, 0, data.length);
        }
        return null;
    }

    protected byte[] loadClassData(String name) {
        try {
            // package -> file folder
            name = name.replace("."."/ /");
            FileInputStream fis = new FileInputStream(new File(classPath + "/ /" + name + ".class"));
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int len = -1;
            byte[] b = new byte[2048];
            while((len = fis.read(b)) ! = -1) {
                baos.write(b, 0, len);
            }
            fis.close();
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null; }}Copy the code

Step 3: Test the loading process of the class loader

CustomerClassLoaderTester.java

  1. When the test program starts, copy and load the source files of the target class to be loaded one by one.
    private static final String CHILDREN_SOURCE_CODE_NAME = SOURCE_CODE_LOCATION + "Children.java";
    private static final String PARENT_SOURCE_CODE_NAME = SOURCE_CODE_LOCATION + "Parent.java";
    private static final List<String> SOURCE_CODE = Arrays.asList(CHILDREN_SOURCE_CODE_NAME, PARENT_SOURCE_CODE_NAME);

    static {
        SOURCE_CODE.stream().map(path -> new File(path))
            // Path to file object.filter(f -> ! f.isDirectory())// File traversal
            .forEach(f -> {
            // Copy the source code
            File targetFile = copySourceFile(f);
            // Compile source code
            compileSourceFile(targetFile);
        });
    }
Copy the code
  1. Copy a single source file to the class loading directory of the custom class loader.
    protected static File copySourceFile(File f) {
        BufferedReader reader = null;
        BufferedWriter writer = null;
        try {
            reader = new BufferedReader(new FileReader(f));
            // package ... ;
            String firstLine = reader.readLine();

            StringTokenizer tokenizer = new StringTokenizer(firstLine, "");
            String packageName = "";
            while (tokenizer.hasMoreElements()) {
                String e = tokenizer.nextToken();
                if (e.contains("package")) {
                    continue;
                } else {
                    packageName = e.trim().substring(0, e.trim().length() - 1); }}// package -> path
            String packagePath = packageName.replace("."."/ /");
            // java file path
            String targetFileLocation = TARGET_CODE_LOCALTION + "/ /" + packagePath + "/ /";

            String sourceFilePath = f.getPath();
            String fileName = sourceFilePath.substring(sourceFilePath.lastIndexOf("\ \") + 1);

            File targetFile = new File(targetFileLocation, fileName);
            File targetFileLocationDir = new File(targetFileLocation);
            if(! targetFileLocationDir.exists()) { targetFileLocationDir.mkdirs(); }// writer
            writer = new BufferedWriter(new FileWriter(targetFile));
            // Write the first line
            writer.write(firstLine);
            writer.newLine();
            writer.newLine();

            String input = "";
            while((input = reader.readLine()) ! =null) {
            writer.write(input);
                writer.newLine();
            }

            return targetFile;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                reader.close();
                writer.close();
            } catch(IOException e) { e.printStackTrace(); }}return null;
    }
Copy the code
  1. After the copy.javaSource files are manually compiled and generated in the same directory.classFile.
    protected static void compileSourceFile(File f) {
        try {
            JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager standardFileManager = javaCompiler.getStandardFileManager(null.null.null);
            Iterable<? extends JavaFileObject> javaFileObjects = standardFileManager.getJavaFileObjects(f);

            // Perform a compilation task
            CompilationTask task = javaCompiler.getTask(null, standardFileManager, null.null.null, javaFileObjects);
            task.call();
            standardFileManager.close();

        } catch(Exception e) { e.printStackTrace(); }}Copy the code
  1. throughCustom class loadersloadingChildrenthejava.lang.Class<? >Object, which is then created using reflectionChildrenInstance object of.
    @Test
    public void test(a) throws Exception {
        // Create a custom class loader
        CustomClassLoader classLoader = new CustomClassLoader(TARGET_CODE_LOCALTION); // E://myclassloader//classpath
        // Load class files into memory dynamically (no connection)Class<? > c = classLoader.loadClass("org.ostenant.jdk8.learning.examples.classloader.custom.Children");
        // Get all the methods by reflection
        Method[] declaredMethods = c.getDeclaredMethods();
        for (Method method : declaredMethods) {
            if ("say".equals(method.getName())) {
                // Get the children object by reflection
                Object children = c.newInstance();
                // Call the children say() method
                method.invoke(children);
                break; }}}Copy the code

Test the class loader written

(I). Test Scenario 1

  1. keepstaticCode block to the target classChildren.javaandParent.javacopyGo to the directory where the class is loaded, and do it manuallycompile.
  2. keepTest the target class in the project directoryChildren.javaandParent.java.

Test result output:

Analysis of test results:

We successfully created the Children object and called its say() method through reflection. However, if you look at the console log, you can see that the class load is still using AppClassLoader, and the CustomClassLoader does not take effect.

View the class loading directory of the CustomClassLoader:

The Parent and Chidren files that we copied and compiled are in the class directory.

Analyze the reasons:

Because Parent. Java and Children. Java are in the project space, they are not removed after copying. Causes AppClassLoader to find and successfully load the target class in its Classpath first.

(2) test Scenario 2

  1. Comment out thestaticCode block (there are compiled target classes in the class directory.classFile).
  2. removeTest the target class in the project directoryChildren.javaandParent.java.

Test result output:

We successfully loaded the target class through the custom class loader. The Children object is created and its say() method is called through reflection.

Now we’re done with a simple classloader of our own!

Reference books

Zhou, zhiming, understanding the Java Virtual Machine in depth: Advanced JVM features and Best Practices, China Machine Press


Welcome to pay attention to the technical public number: Zero one Technology Stack

This account will continue to share learning materials and articles on back-end technologies, including virtual machine basics, multithreaded programming, high-performance frameworks, asynchronous, caching and messaging middleware, distributed and microservices, architecture learning and progression.