Analysis of resource loading in Java through source code

premise

Recently, a basic component project just needs to use the resource loading in JDK. The resources mentioned here include class files and other static resources. It just needs to replenish the relevant knowledge of class loader and resource loading and organize it into an article.

Understand how classes work

This section mainly analyzes the class loader and the parental delegation model.

What is a class loader

The virtual machine design team put the action of "obtaining the binary byte stream describing this class through the fully qualified name of a class" in the class loading stage into the external implementation of the Java virtual machine, so that the application can decide how to obtain the required class, and the code module implementing this action is called "classloader".

Although the class loader is only used to realize the function of class loading, its role in Java programs is not limited to the class loading stage. For any class, the class loader that loads it and the class itself need to establish the uniqueness of the class in the Java virtual machine. Each class loader has an independent class namespace. The above sentence is intuitively: comparing whether two classes are "equal" is meaningful only if the two classes are loaded by the same class loader. Otherwise, even if the two classes come from the same class file and are loaded by the same virtual machine, as long as the class loaders that load them are different, the two classes must be "unequal". The "equality" mentioned here includes the return results of the equals () method, isassignablefrom () method and isinstance () method of the class object representing the class, as well as the determination of the object ownership relationship using the instanceof keyword.

Class and the class loader that loads it determine the uniqueness of the class in the Java virtual machine. This feature provides the basis for the later technologies such as hot update class and hot deployment.

Parents Delegation Model

From the perspective of Java virtual machine, there are only two different class loaders:

Several system level class loaders are provided in the JDK:

Java applications developed by Java developers are class loaded by the above four kinds of loaders. If necessary, you can also add custom class loaders. There is a certain relationship among startup class loader, extension class loader, application class loader and user-defined class loader:

The hierarchical relationship between class loaders shown in the figure above is called the parents delegation model. The parent delegation model requires that except for the top-level class loader (the top-level class loader in Java is usually bootstrap classloader), other class loaders should have their own parent class loader. The parent-child relationship between these class loaders is generally not implemented by inheritance, but by composition. The hierarchical relationship of class loader can be verified by the following code:

public class Main {

	public static void main(String[] args) throws Exception{
		ClassLoader classLoader = Main.class.getClassLoader();
		System.out.println(classLoader);
		System.out.println(classLoader.getParent());
		System.out.println(classLoader.getParent().getParent());
	}
}

//输出结果,最后的null说明是Bootstrap ClassLoader
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@4629104a
null

Working mechanism of parent delegation model: if a class loader receives a class loading request, it will not try to load the class itself, but delegate the request to the parent class loader to complete it. This is true for class loaders at each level, so all class loading requests should eventually be transmitted to the top-level class loader, Only when the parent class loader feeds back that it cannot complete the current class loading request (that is, it does not find the required class in its search scope), the child class loader will try to load the class by itself. However, it should be noted that each class loader caches the loaded class, that is, repeatedly loading an existing class, it will be loaded from the loaded cache. If it is judged from the cache loaded by the current class, it will return directly, otherwise it will delegate the class loading request to the parent class loader. This caching mechanism exists in both appclassloader and extensionclassloader. Bootstrap classloader is unknown.

Advantages of the two parent delegation model: the two parent delegation model is used to organize the relationship between class loaders. A significant advantage is that the Java class has a hierarchical relationship with priority with the class loader that loads it. For example, Java Lang package, which is stored in rt.jar, no matter which class is used to load Java. Jar The classes in Lang package are ultimately delegated to the startup class loader at the top level of the model for loading, so Java Classes in Lang package, such as Java Lang. object class loads the same class in various loader environments in the application. Imagine if you can use a user-defined classloader to load Java Lang. object, multiple Java. Objects will appear in the user application Lang. object class, the most basic type in the Java type system, also has many types. The basic behavior of the type system cannot be guaranteed, and the application will tend to be chaotic. If you try to write a class with the same name that already exists in rt.jar and load it through custom class loading, you will receive an exception thrown by the virtual machine.

Implementation of parental delegation model: the implementation of parental delegation model of class loader is now in the source code of classloader, mainly in classloader #loadclass().

public Class<?> loadClass(String name) throws ClassNotFoundException {
    return loadClass(name,false);
}

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 {
                //父加载器不为null,说明父加载器不是BootstrapClassLoader
                if (parent != null) {
                    c = parent.loadClass(name,false);
                } else {
                    //父加载器为null,说明父加载器是BootstrapClassLoader
                    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;
     }
}

Destroy the parental delegation model

The parental delegation model has been "destroyed" three times in the history of java development:

Resource loading API provided in JDK

I spent a lot of time on analyzing the preheating knowledge of class loader because the resource loading in JDK depends on class loader (in fact, class file is a kind of resource file, and the process of class loading is also the process of resource loading). Here, we will list the API for resource loading commonly used in JDK. Let's first look at the methods provided in classloader.

Resource loading API provided by classloader

//1.实例方法

public URL getResource(String name)

//这个方法仅仅是调用getResource(String name)返回URL实例直接调用URL实例的openStream()方法
public InputStream getResourceAsStream(String name)

//这个方法是getResource(String name)方法的复数版本
public Enumeration<URL> getResources(String name) throws IOException

//2.静态方法

public static URL getSystemResource(String name)

//这个方法仅仅是调用getSystemResource(String name)返回URL实例直接调用URL实例的openStream()方法
public static InputStream getSystemResourceAsStream(String name)

//这个方法是getSystemResources(String name)方法的复数版本
public static Enumeration<URL> getSystemResources(String name)

Overall, there are only two methods to analyze: getresource (string name) and getsystemresource (string name). View the source code of getresource (string name):

public URL getResource(String name) {
    URL url;
    if (parent != null) {
        url = parent.getResource(name);
    } else {
        url = getBootstrapResource(name);
    }
    if (url == null) {
        url = findResource(name);
    }
    return url;
}

Deja vu? It is obvious that a similar parental delegation model is used to load resources during class loading. This method is described in the API comments. It is usually used to load data resources, such as images, audio, text, etc. the resource name needs to use the path separator '/'. The root path found in getresource (string name) method can be verified by the following methods:

public class ResourceLoader {

	public static void main(String[] args) throws Exception {
		ClassLoader classLoader = ResourceLoader.class.getClassLoader();
		URL resource = classLoader.getResource("");
		System.out.println(resource);
	}
}

//输出:file:/D:/Projects/rxjava-seed/target/classes/

Obviously, the output result is the classpath of the current application. To sum up, classloader #getresource (string name) is a search resource based on the classpath of the user application. The resource name must use the path separator '/' to separate the directory, but '/' cannot be used as the beginning of the resource name, that is, classloader getResource("/img/doge.jpg")。 Next, let's look at the source code of classloader #getsystemresource (string name):

public static URL getSystemResource(String name) {
    //实际上Application ClassLoader一般不会为null
    ClassLoader system = getSystemClassLoader();
    if (system == null) {
        return getBootstrapResource(name);
    }
    return system.getResource(name);
}

This method preferentially uses the application class loader for resource loading. If the application class loader is null (in fact, this is rare), the startup class loader is used for resource loading. If the application class loader is not null, it actually degenerates into a classloader #getresource (string name) method.

To sum up: the core method of the resource loading method provided by classloader is classloader #getresource (string name). It searches resources based on the classpath of the user application and follows the "parent delegation model for resource loading". The resource name must use the path separator '/' to separate directories, but cannot use '/' as the starting character of the resource name, Other methods are based on this method for derivation, adding complex operations and other operations. The getresource (string name) method will not display and throw an exception. When the resource search fails, it will return null.

Resource loading API provided by class

java. Lang. class also provides resource loading methods, as follows:

public java.net.URL getResource(String name) {
    name = resolveName(name);
    ClassLoader cl = getClassLoader0();
    if (cl==null) {
        // A system class.
        return ClassLoader.getSystemResource(name);
    }
    return cl.getResource(name);
}

public InputStream getResourceAsStream(String name) {
    name = resolveName(name);
    ClassLoader cl = getClassLoader0();
    if (cl==null) {
        // A system class.
        return ClassLoader.getSystemResourceAsStream(name);
    }
    return cl.getResourceAsStream(name);
}

From the above source code, class #getresource (string name) and class #getresourceasstream (string name) are only one step more than classloader #getresource (string name) and classloader #getresourceasstream (string name) respectively, that is, the preprocessing resolvename (name) of the bank's resource name before searching. We focus on what this method does:

private String resolveName(String name) {
    if (name == null) {
        return name;
    }
    if (!name.startsWith("/")) {
        Class<?> c = this;
        while (c.isArray()) {
            c = c.getComponentType();
        }
        String baseName = c.getName();
        int index = baseName.lastIndexOf('.');
        if (index != -1) {
            name = baseName.substring(0,index).replace('.','/')
                    +"/"+name;
         }
    } else {
         name = name.substring(1);
    }
    return name;
}

The logic is relatively simple:

Summary: if you read an article about URLs and URIs I wrote before, it will be clear that in fact, the resource name processing of class #getresource (string name) and class #getresourceasstream (string name) is similar to the processing of relative URLs, and the root path of "processing of relative URLs" is the classpath of the application. If the resource name starts with '/', it is equivalent to loading resources from classpath. If the resource name does not start with '/', it is equivalent to loading resources under the package directory based on the actual type of the current class.

In fact, similar resource loading methods also exist in the file class, which will not be expanded here.

Summary

Understanding the resource loading method in JDK is helpful to write some general basic components, such as resourceloader and classpathresource in spring. The more practical tools here are also written based on the JDK resource loading method. The protagonist in the next blog post "analysis of the source code of serviceloader in JDK" is the function implementation based on class loader. It is also the core class of service class loading in SPI.

To tell you the truth, "parent delegation model" and "destroy parent delegation model" of class loader are common interview questions. Here are two interview questions:

I hope this article can help you understand and solve these two problems.

reference material:

(end of this article c-1-d e-20181014)

The official account of Technology (Throwable Digest), which is not regularly pushed to the original technical article (never copied or copied):

Entertainment official account ("sand sculpture"), select interesting sand sculptures, videos and videos, push them to relieve life and work stress.

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>