JVM family of loading processes – custom class loaders

Honestly, class loading process, the author is familiar with and have actual combat experience, because there was a custom class loader of actual combat experience, the article finally will share with you, though most junior partner feel this part of coding no actual meaning, if you keep writing the CRUD and business framework in existing senior language, I can tell you, It really doesn’t help. But then again, if you want to learn more about the underlying layers and do some tricks with class loading, then this is a very important area to learn. Many frameworks take advantage of the dynamic loading feature in the class loading mechanism to get things done, such as the well-known OSGI modularization (one class loader per module), JSP(runtime conversion to byte stream for the loader to load dynamically),Tomcat(custom class loaders used to isolate different projects)… I won’t list them all here. This article will first talk about the class loading flow, and then share the author’s experience of a custom class loading, the summary is as follows:

Article structure 1 Class loading process description 2 Custom class loader description 3 Actual custom class loader

1. Class loading process description

The author found the diagram on the Internet and drew a class life cycle flow chart by referring to himself:

Life cycle diagram of a class

Note: The processes in the figure are not in strict order. For example, when loading 1, the verification of 2 has already started, and it is carried out across.

loading

The loading phase is basically what compiles us. Class static files are converted into memory (the method area) and then exposed for programmers to access. Specific expansion:

  • Get the binary byte stream that defines a class by its fully qualified name (it can be a.class file, IO on the network, zip package, etc.)
  • Transform the static storage structure represented by this byte stream into the runtime data structure of the method area.
  • Generate a java.lang.Class object representing the Class in memory (which HotSpot implementations actually do in the method area), which acts as an access point to the various data of the Class in the method area.

validation

The binary byte stream obtained during loading does not necessarily come from a.class file, such as from the network, so it cannot be loaded without some form checking. So the validation phase is really about protecting the JVM. For ordinary Javaers, we are all.class files compiled from.java files and then converted to the corresponding binary stream without harm. So don’t worry too much about that part.

To prepare

The preparation phase consists of allocating memory (in the method area) to static variables and setting their initial values. Public static Integer value =1; The value in the preparation phase is actually zero. Public static final Integer value =1; public static final Integer value =1; In the preparation phase value is assigned to 1;

parsing

The parsing phase is a little bit more abstract, just to say a little bit, because it’s not so important, but there are two concepts, symbolic reference, direct reference. Call new B() in class A; So if you think about it, after we compile the.class file, we still have this correspondence, but in the form of bytecode instructions, like “Invokespecial #2”, and you can guess that #2 is actually our class B, so when WE execute this line of code, This is a static object. If class B has already been loaded into the method area with the address (# F00123), we need to convert this #2 into this address (# F00123) so that the JVM knows where class B is and calls it. Other things, like symbolic references to methods, and symbolic references to constants, all mean the same thing, and you have to understand that methods, constants, and classes are high-level Java concepts, and in a.class file, it doesn’t matter what you are. All exist in the form of instructions, so you want to translate that reference relationship (who calls whom, who references whom) into the form of address instructions. All right. That’s colloquial enough. I think you can make sense of it. This is actually not very important, for most of the coder, so I’m just going to talk about it in a general way.

Initialize the

Class constructors are automatically generated. Static variable assignments +static code blocks are assembled in the order in which they appear. Note :1 Static variables are allocated and initialized in the preparation phase.2 A class can be executed concurrently by many threads, and the JVM locks it to ensure simplicity. Avoid thread blocking.

Use & Uninstall

Use is when you use new stance directly or through reflection. NewInstance. The unloading is automatic and the GC is also collected in the side area. But the conditions are very harsh, interested in you can have a look, generally will not uninstall the class.

2. Custom class loaders explained

2.1 Class loaders

Classloaders are the classes that perform the above classloading process. The system has some classloaders by default. From the perspective of the JVM, there are only two classloaders:

  • Bootstrap ClassLoader: implemented by C++ (for HotSpot), responsible for storing<JAVA_HOME>The libraries are loaded into memory in the /lib directory or in the path specified by the -xbootclasspath parameter.
  • Other classloaders: Implemented in the Java language and inherited from the abstract ClassLoader class. Such as:
    • Extension ClassLoader: Takes care of loading<JAVA_HOME>All class libraries in the /lib/ext directory or in the path specified by the java.ext.dirs system variable.
    • The Application ClassLoader. The class loader is responsible for loading specified libraries on the user’s classpath, and we can use this class loader directly. In general, if we don’t have a custom class loader this is the default.
    • Custom class loader, users according to the requirements of their own definition. Also needs to inherit from the ClassLoader.

2.2 Parental delegation model

If a classloader receives a class-loading request, it first does not attempt to load the class itself, but delegates the request to the parent classloader. This is true for every classloader, and only if the parent cannot find the specified class in its search scope (that is, ClassNotFoundException) will the child loader attempt to load it itself. See below:

Parental delegation model

Note that custom classloaders may not follow the parent delegate model, but the transfer relationship shown in red is predefined by the JVM and cannot be changed by anyone. What are the benefits of the parental delegation model? For example, if someone deliberately defines a String class in their code with the same package name and class name as the JDK, then according to the parent delegate model, the class loader will first be passed to the parent class loader to load, and eventually to the launcher class loader, which determines that the class has been loaded. So the programmer’s custom String class will not be loaded. Avoid programmer’s own random string of system – level classes.

2.3 Customizing class loaders

I can’t wait to get into the code after all this theory. Let’s take a look at how to customize class loaders and how to follow the parent delegate model (upward transitivity) when customizing loaders. In this case, the JDK uses the template design pattern. The upward transitivity is already wrapped up in the ClassLoader, and is implemented in the loadClass method:

protectedClass<? > loadClass(String name,boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // 1. Check whether it has been loaded.
        Class c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if(parent ! =null) {
                //2. If not, call the parent class loader to load it
                    c = parent.loadClass(name, false);
                } else {
                If there is no parent class loader, use BootstrapClassLoader to load itc = findBootstrapClassOrNull(name); }}catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                //3. If the parent loader is not loaded, call findClass to load it
                long t1 = System.nanoTime();
                c = findClass(name);

                // this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); }}if (resolve) {
            resolveClass(c);
        }
        returnc; }}Copy the code

LoadClass (String, Boolean) implements the parent delegate model. The general process is as follows:

  1. Check to see if the specified class has already been loaded. If it has been loaded, it does not need to be loaded and returns directly.
  2. If this class has not been loaded, then check if there is a parent loader; If there is a parent loader, it is loaded by the parent (that is, call parent-loadclass (name, false);) . Or call the bootstrap class loader to load it.
  3. If neither the parent nor the Bootstrap class loader can find the specified class, the findClass method of the current class loader is called to complete the class loading. The default findClass does nothing and throws a ClassNotFound exception, so our custom classloader will override this method.
  4. ApplicationClassLoader findClass is loaded in the classpath directory, ExtentionClassLoader is loaded in the java_HOME /lib/ext directory. It’s just that the findClass method is different.

As you can see from the above, the findClass function of the abstract ClassLoader throws an exception by default. If the parent loader is unable to load a Class, loadClass calls the findeClass function of our custom Class loader. Therefore, we must implement the loadClass function to convert a specified Class name into a Class object. This works fine if the class reads a byte array with a specified name. But how do you turn a byte array into a Class object? Quite simply, Java provides the defineClass method, which converts a byte array into a Class object

DefineClass: Converts a byte array into a Class object. This byte array is the final byte array after the Class file is read.

protected finalClass<? > defineClass(String name,byte[] b, int off, int len)
        throws ClassFormatError  {
        return defineClass(name, b, off, len, null);Copy the code

Above this paper introduces the principle of the custom class loaders and several important methods (loadClass, findClass defineClass), believe most junior partner or a face is veiled, it doesn’t matter, I a figure on the first, and then a custom class loader:

Custom class loader method call flowchart


import java.io.InputStream;
public class MyClassLoader extends ClassLoader
{
    public MyClassLoader(a)
    {}public MyClassLoader(ClassLoader parent)
    {
        // The parent ClassLoader must not be ApplicationClassLoader or findClass will not be executed
        super(parent);
    }
    @Override
    protectedClass<? > findClass(String name)throws ClassNotFoundException
    {
    //1. Overwrite findClass to find the.class file and return the class object
        try
        {
            String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
            InputStream is = getClass().getResourceAsStream(fileName);
            if (is == null) {
            //2. If not found, return null
                return null;
            }
            byte[] b = new byte[is.available()];
            is.read(b);
            //3. Convert byte arrays to Class objects
            return defineClass(name, b, 0, b.length);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        return null; }}Copy the code

Quick recap: It’s really simple. Inherit the ClassLoader object and override the findClass method, which finds a. Class file, converts it to a byte array, and calls defineClass to convert it to a class object and returns it. So easy.. To demonstrate the effect:

        MyClassLoader mcl = newMyClassLoader(); Class<? > c1 = Class.forName("Student".true, mcl);
        Object obj = c1.newInstance();
        System.out.println(obj.getClass().getClassLoader());
        System.out.println(obj instanceof Student);Copy the code

Sun.misc.Launcher$AppClassLoader@6951a712 true is returned

        MyClassLoader mcl = newMyClassLoader(ClassLoader.getSystemClassLoader().getParent()); Class<? > c1 = Class.forName("Student".true, mcl);
        Object obj = c1.newInstance();
        System.out.println(obj.getClass().getClassLoader());
        System.out.println(obj instanceof Student);Copy the code

The result is MyClassLoader@3918d722 false

Key analysis: the first code and the second is only a little different in the new MyClassLoader (), an incoming. This getSystemClassLoader () getParent (); (This is actually an extension class loader.)

  1. When this value is not passed in, the default parent ClassLoader is Application ClassLoader, so you can know that the Student class is already loaded in this loader. When we call Class. ForName and pass in the custom Class loader, we call the loadClass of the custom Class loader and determine that it has not been loaded before. Then we call the loadClass of the parent Class (ApplicationClassLoader). So go straight back. So print ClassLoader as AppClassLoader. Verify that the default parent class loader is ApplicationClassLoader:

         MyClassLoader mcl = new MyClassLoader();
         System.out.println(mcl.getParent().getClass());Copy the code

    The result is class sun.misc.Launcher$AppClassLoader

  2. When we pass the parent class loader as the extension class loader, when we call the loadeClass of the parent class (the extension class loader), because the extension class loader only loads classes in the java_HOME /lib/ext directory, Therefore, it cannot be loaded in the classpath path, returns null, according to loadClass logic, then calls the custom class loader findClass to load. So print the ClassLoader as MyClassLoader.

  3. Instanceof returns true if (class loader + class) is the same, although here we are all one Student class, one file, but loaded by two class loaders, of course returns false.
  4. The only criteria for judging a class in the JVM is the same (classloader +.class file). Things like Instanceof and casts are such standards.
  5. Note that the parent class loader is not implemented as an inheritance, but as a member variable. When the call constructor is passed in, it sets its own member variable parent as the incoming loader.
  • LoadClass can also be overwritten. For example, if you overwrite loadClass, you can implement “load file directly”. “Not determining whether the parent class has been loaded” breaks the parent delegate model and is generally not recommended. But you guys can try it.

Custom class loader will give everyone had said, although the author feeling already clear, because nothing is the problem of a few methods (loadClass, findClass defineClass), but still give you a few portal, you can read more, see each other once: www.cnblogs.com/xrq730/p/48… www.importnew.com/24036.html

3. Customize class loaders

In fact, the above basic has been the custom class loader to speak clearly, here and we share the author a practical writing custom class loader experience. The background is as follows: We used an open source communication framework in the project, but we changed the source code and made some customization changes. Let’s assume that version A was used before the source code was changed, and version B was changed after the source code was changed. Because part of the project code needs to use version A, and part of the code needs to use version B. All package names and class names are the same in versions A and B. The problem is that if you rely only on the ApplicationClassLoader, it will only load the version closest to the ClassPath. The remaining version is loaded according to the parent delegate model. So you need to customize a class loader here. The general idea is as follows:

Double edition design drawing

It is important to note that the parent class loader must be set to ExtentionClassLoader when you customize the class loader. If this is not set, according to the parent delegate model, the default parent class loader is ApplicationClassLoader. Determines that it has been loaded (version A and version B package name and class name are the same) and returns the loaded version of VERSION A directly instead of calling the subclass’s findClass. FindClass of our custom classloader would not be called to load version B remotely.

By the way, the author’s implementation here is to follow the parent delegate model. If the author does not follow the parent delegate model, it is possible to create a custom class loader that overrides the loadClass method and instead calls the findClass method to load version B. You have to be flexible with your code.

conclusion

Well, the JVM class loading mechanism to share with you, I hope you can think of a custom class loader to solve the practical problems. Have a good day .