The whole process of class loading and running
package com.tuling.jvm; public class Math { public static final int initData = 666; public static User user = new User(); public int compute() { //A method corresponds to a stack frame memory area int a = 1; int b = 2; int c = (a + b) * 10; return c; } public static void main(String[] args) { Math math = new Math(); math.compute(); } }
The general process of executing code through Java commands is as follows:
The class loading process of loadClass includes the following steps:
Load > > verify > > prepare > > parse > > initialize > > use > > uninstall
- Loading: find the bytecode file on the hard disk and read it through IO. It will be loaded only when the class is used. For example, call the main() method of the class, new object, etc. in the loading stage, a Java. Java representing this class will be generated in memory Lang. class object, as the access entry of various data of this class in the method area
- Verification: verify the correctness of bytecode file
- Preparation: allocate memory to the static variables of the class and assign default values
Parsing: replace symbolic references with direct references. In this stage, some static methods (symbolic references, such as main() method) will be replaced with pointers or handles pointing to the memory stored in the data (direct references). This is the so-called static linking process (completed during class loading). Dynamic linking is completed during program operation. Replace symbolic references with direct references, The next lesson will talk about dynamic links
- Initialization: initialize the static variable of the class to the specified value and execute the static code block
Note that if other classes are used during the operation of the main class, these classes will be loaded step by step. The classes in the jar package or war package are not loaded all at once, but only when they are used.
public class TestDynamicLoad { static { System.out.println("*************load TestDynamicLoad************"); } public static void main(String[] args) { new A(); System.out.println("*************load test************"); B b = null; //B will not load unless new B() is executed here } } class A { static { System.out.println("*************load A************"); } public A() { System.out.println("*************initial A************"); } } class B { static { System.out.println("*************load B************"); } public B() { System.out.println("*************initial B************"); } } Operation results: *************load TestDynamicLoad************ *************load A************ *************initial A************ *************load test************
Classloader and parental delegation mechanism
The above class loading process is mainly realized through class loaders. There are the following types of loaders in Java:
- Boot class loader: it is responsible for loading the core class libraries under the lib directory of JRE that support the operation of the JVM, such as rt.jar and charsets Jar, etc
- Extension class loader: it is responsible for loading the JAR class package in the ext extension directory under the lib directory of JRE that supports the operation of the JVM
- Application class loader: it is responsible for loading the class package under the ClassPath path, mainly those classes written by yourself
- Custom loader: it is responsible for loading the class package under the user-defined path
Take a look at an example of a class loader:
public class TestJDKClassLoader { public static void main(String[] args) { System.out.println(String.class.getClassLoader()); System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName()); System.out.println(TestJDKClassLoader.class.getClassLoader().getClass().getName()); System.out.println(); ClassLoader appClassLoader = ClassLoader.getSystemClassLoader(); ClassLoader extClassloader = appClassLoader.getParent(); ClassLoader bootstrapLoader = extClassloader.getParent(); System.out.println("the bootstrapLoader : " + bootstrapLoader); System.out.println("the extClassloader : " + extClassloader); System.out.println("the appClassLoader : " + appClassLoader); System.out.println(); System.out.println("bootstrapLoader Load the following files:"); URL[] urls = Launcher.getBootstrapClassPath().getURLs(); for (int i = 0; i < urls.length; i++) { System.out.println(urls[i]); } System.out.println(); System.out.println("extClassloader Load the following files:"); System.out.println(System.getProperty("java.ext.dirs")); System.out.println(); System.out.println("appClassLoader Load the following files:"); System.out.println(System.getProperty("java.class.path")); } } Operation results: null sun.misc.Launcher$ExtClassLoader sun.misc.Launcher$AppClassLoader the bootstrapLoader : null the extClassloader : sun.misc.Launcher$ExtClassLoader@3764951d the appClassLoader : sun.misc.Launcher$AppClassLoader@14dad5dc bootstrapLoader Load the following files: file:/D:/dev/Java/jdk1.8.0_45/jre/lib/resources.jar file:/D:/dev/Java/jdk1.8.0_45/jre/lib/rt.jar file:/D:/dev/Java/jdk1.8.0_45/jre/lib/sunrsasign.jar file:/D:/dev/Java/jdk1.8.0_45/jre/lib/jsse.jar file:/D:/dev/Java/jdk1.8.0_45/jre/lib/jce.jar file:/D:/dev/Java/jdk1.8.0_45/jre/lib/charsets.jar file:/D:/dev/Java/jdk1.8.0_45/jre/lib/jfr.jar file:/D:/dev/Java/jdk1.8.0_45/jre/classes extClassloader Load the following files: D:\dev\Java\jdk1.8.0_45\jre\lib\ext;C:\Windows\Sun\Java\lib\ext appClassLoader Load the following files: D:\dev\Java\jdk1.8.0_45\jre\lib\charsets.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\deploy.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\access-bridge-64.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\cldrdata.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\dnsns.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\jaccess.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\jfxrt.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\localedata.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\nashorn.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\sunec.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\sunjce_provider.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\sunmscapi.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\sunpkcs11.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\zipfs.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\javaws.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\jce.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\jfr.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\jfxswt.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\jsse.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\management-agent.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\plugin.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\resources.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\rt.jar;D:\ideaProjects\project-all\target\classes;C:\Users\zhuge\.m2\repository\org\apache\zookeeper\zookeeper\3.4.12\zookeeper-3.4.12.jar;C:\Users\zhuge\.m2\repository\org\slf4j\slf4j-api\1.7.25\slf4j-api-1.7.25.jar;C:\Users\zhuge\.m2\repository\org\slf4j\slf4j-log4j12\1.7.25\slf4j-log4j12-1.7.25.jar;C:\Users\zhuge\.m2\repository\log4j\log4j\1.2.17\log4j-1.2.17.jar;C:\Users\zhuge\.m2\repository\jline\jline\0.9.94\jline-0.9.94.jar;C:\Users\zhuge\.m2\repository\org\apache\yetus\audience-annotations\0.5.0\audience-annotations-0.5.0.jar;C:\Users\zhuge\.m2\repository\io\netty\netty\3.10.6.Final\netty-3.10.6.Final.jar;C:\Users\zhuge\.m2\repository\com\google\guava\guava\22.0\guava-22.0.jar;C:\Users\zhuge\.m2\repository\com\google\code\findbugs\jsr305\1.3.9\jsr305-1.3.9.jar;C:\Users\zhuge\.m2\repository\com\google\errorprone\error_prone_annotations\2.0.18\error_prone_annotations-2.0.18.jar;C:\Users\zhuge\.m2\repository\com\google\j2objc\j2objc-annotations\1.1\j2objc-annotations-1.1.jar;C:\Users\zhuge\.m2\repository\org\codehaus\mojo\animal-sniffer-annotations\1.14\animal-sniffer-annotations-1.14.jar;D:\dev\IntelliJ IDEA 2018.3.2\lib\idea_rt.jar
Class loader initialization process:
See the diagram of the whole process of class running and loading, which will create the JVM initiator instance sun misc. Launcher.
Inside the launcher constructor, it creates two class loaders, sun misc. Launcher. Extclassloader and sun misc. Launcher. Appclassloader (application class loader).
By default, the JVM uses the instance of AppClassLoader returned by the Launcher's getClassLoader() method to load our application
//Construction method of Launcher public Launcher() { Launcher.ExtClassLoader var1; try { //Construct an extension class loader and set its parent loader to null during construction var1 = Launcher.ExtClassLoader.getExtClassLoader(); } catch (IOException var10) { throw new InternalError("Could not create extension class loader", var10); } try { //Construct an application class loader, and set its parent loader to ExtClassLoader during construction, //The loader attribute value of the Launcher is AppClassLoader. We usually use this class loader to load applications written by ourselves this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); } catch (IOException var9) { throw new InternalError("Could not create application class loader", var9); } Thread.currentThread().setContextClassLoader(this.loader); String var2 = System.getProperty("java.security.manager"); . . . . . . //Omit some code that you don't need to pay attention to }
Parental delegation mechanism
The JVM class loader has a parent-child hierarchy, as shown in the following figure
In fact, there is a parental delegation mechanism for class loading. When loading a class, it will first entrust the parent loader to find the target class, and then entrust the upper parent loader to load if it cannot be found. If all parent loaders cannot find the target class under their own loading class path, they will find and load the target class in their own class loading path.
For example, our Math class will first find the application class loader to load. The application class loader will first delegate the extension class loader to load, and then delegate the boot class loader. If the top-level boot class loader fails to find the Math class in its class loading path for a long time, it will return the request to load the Math class, After receiving the reply, the extended class loader loads itself. After looking for the Math class in its own class loading path for a long time, it fails to find the Math class, and then returns the Math class loading request to the application class loader. The application class loader then looks for the Math class in its own class loading path. As a result, it loads itself..
The parent delegation mechanism is simpler, that is, first find the father to load, and then the son to load
Let's look at the source code of the parent delegation mechanism of the application class loader AppClassLoader loading class. The loadClass method of AppClassLoader will eventually call the loadClass method of its parent ClassLoader. The general logic of this method is as follows:
1. First, check whether the class with the specified name has been loaded. If it has been loaded, there is no need to load it again and return directly.
2. If this class has not been loaded, then judge whether there is a parent loader; If there is a parent loader, it will be loaded by the parent loader (that is, call parent.loadClass(name, false);) Or call the bootstrap class loader to load.
3. If neither the parent loader nor the bootstrap class loader can find the specified class, call the findClass method of the current class loader to complete the class loading.
//The loadClass method of ClassLoader implements the parental delegation mechanism protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // Check whether the current class loader has loaded the class Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { //If the parent loader of the current loader is not empty, delegate the parent loader to load the class c = parent.loadClass(name, false); } else { //If the parent loader of the current loader is empty, delegate the boot class loader to load the class 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(); //Will call the findClass method of URLClassLoader to find and load the class in the classpath of the loader 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) { //Will not execute resolveClass(c); } return c; } }
Why design a parental delegation mechanism?
- Sandbox security mechanism: written in Java lang.String. Class class will not be loaded, which can prevent the core API library from being tampered with at will
- Avoid repeated loading of classes: when the father has loaded the class, it is not necessary to load the child ClassLoader again to ensure the uniqueness of the loaded class
See an example of class loading:
package java.lang; public class String { public static void main(String[] args) { System.out.println("**************My String Class**************"); } } Operation results: error: In class java.lang.String Not found in main method, Please main Method is defined as: public static void main(String[] args) otherwise JavaFX Application classes must be extended javafx.application.Application
Overall responsibility entrustment mechanism
"Overall responsibility" means that when a ClassLoder loads a class, unless another ClassLoder is used, the classes that the class depends on and references are also loaded by this ClassLoder.
Example of custom class loader:
The custom class loader only needs to inherit Java Lang. classloader class, which has two core methods, one is loadClass(String, boolean), which implements the two parent delegation mechanism, and the other is findClass, which is empty by default. Therefore, our custom class loader mainly rewrites the findClass method.
public class MyClassLoaderTest { static class MyClassLoader extends ClassLoader { private String classPath; public MyClassLoader(String classPath) { this.classPath = classPath; } 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(); return data; } protected Class<?> findClass(String name) throws ClassNotFoundException { try { byte[] data = loadByte(name); //defineClass converts a byte array into a class object. This byte array is the final byte array after the class file is read. return defineClass(name, data, 0, data.length); } catch (Exception e) { e.printStackTrace(); throw new ClassNotFoundException(); } } } public static void main(String args[]) throws Exception { //When initializing the custom class loader, the parent class ClassLoader will be initialized first, and the parent loader of the custom class loader will be set as the application class loader AppClassLoader MyClassLoader classLoader = new MyClassLoader("D:/test"); //Create test / COM / Turing / JVM directories on disk D, and copy the User class to User1 Class drop into this directory Class clazz = classLoader.loadClass("com.tuling.jvm.User1"); Object obj = clazz.newInstance(); Method method = clazz.getDeclaredMethod("sout", null); method.invoke(obj, null); System.out.println(clazz.getClassLoader().getClass().getName()); } } Operation results: =======Load the class and call the method with your own loader======= com.tuling.jvm.MyClassLoaderTest$MyClassLoader
Break the parental delegation mechanism
Another example of sandbox security mechanism is to try to break the two parent delegation mechanism and load our own java with a custom class loader lang.String. class
public class MyClassLoaderTest { static class MyClassLoader extends ClassLoader { private String classPath; public MyClassLoader(String classPath) { this.classPath = classPath; } 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(); return data; } 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(); } } /** * Rewrite the class loading method to implement its own loading logic and do not delegate it to parents * @param name * @param resolve * @return * @throws ClassNotFoundException */ 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) { // 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.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } if (resolve) { resolveClass(c); } return c; } } } public static void main(String args[]) throws Exception { MyClassLoader classLoader = new MyClassLoader("D:/test"); //Try to use your own rewriting class loading mechanism to load your own Java lang.String. class Class clazz = classLoader.loadClass("java.lang.String"); Object obj = clazz.newInstance(); Method method= clazz.getDeclaredMethod("sout", null); method.invoke(obj, null); System.out.println(clazz.getClassLoader().getClass().getName()); } } Operation results: java.lang.SecurityException: Prohibited package name: java.lang at java.lang.ClassLoader.preDefineClass(ClassLoader.java:659) at java.lang.ClassLoader.defineClass(ClassLoader.java:758)
Tomcat breaks the parental delegation mechanism
Take Tomcat class loading as an example. Can Tomcat use the default parental delegation class loading mechanism?
Let's think about it: Tomcat is a web container, so what problems does it want to solve:
- A web container may need to deploy two applications. Different applications may depend on different versions of the same third-party class library. It is not required that the same class library is only one on the same server. Therefore, it is necessary to ensure that the class libraries of each application are independent and isolated from each other.
- Deployed in the same web container, the same class library and the same version can be shared. Otherwise, if the server has 10 applications, 10 copies of the same class library should be loaded into the virtual machine.
- The web container also has its own dependent class library, which can not be confused with the class library of the application. For security reasons, the class library of the container should be isolated from the class library of the program.
- The web container should support the modification of jsp. As we know, the jsp file must be compiled into a class file to run in the virtual machine, but it is common to modify jsp after the program runs. The web container needs to support the modification of jsp without restarting.
Let's take another look at our question: can Tomcat use the default parental delegation class loading mechanism*
The answer is No. Why?
First, if you use the default class loader mechanism, you cannot load different versions of the same class library. The default class loader only cares about your fully qualified class name, no matter what version you are.
The second problem is that the default class loader can be implemented because its responsibility is to ensure uniqueness.
The third question is the same as the first.
Let's look at the fourth question. We think how to realize the hot loading of jsp files. jsp files are actually class files. If they are modified, but the class name is still the same, the class loader will directly take the existing ones in the method area, and the modified jsp will not be reloaded. So what? We can directly uninstall the class loader of the jsp file, so you should think that each jsp file corresponds to a unique class loader. When a jsp file is modified, we can directly uninstall the jsp class loader. The jsp class is created and reloaded.
Tomcat custom loader details
Several main class loaders of tomcat:
- commonLoader: Tomcat's most basic class loader. The classes in the loading path can be accessed by the Tomcat container itself and various webapps;
- catalinaLoader: the private class loader of Tomcat container. The class in the loading path is not visible to Webapp;
- sharedLoader: the class loader shared by each Webapp. The class in the loading path is visible to all webapps, but not to Tomcat container;
- WebappClassLoader: the private class loader of each Webapp. The classes in the loading path are only visible to the current Webapp. For example, load the relevant classes in the war package. Each war package application has its own WebappClassLoader to realize mutual isolation. For example, different war package applications have introduced different spring versions, so that the implementation can load their own spring versions;
As can be seen from the delegation relationship in the figure:
The classes that can be loaded by CommonClassLoader can be used by CatalinaClassLoader and SharedClassLoader, so as to realize the sharing of public class libraries, while the classes that CatalinaClassLoader and SharedClassLoader can load themselves are isolated from each other.
WebAppClassLoader can use the classes loaded by SharedClassLoader, but the WebAppClassLoader instances are isolated from each other.
The loading range of JasperLoader is only the one compiled by this JSP file Class file, which appears to be discarded: when the Web container detects that the JSP file has been modified, it will replace the current instance of JasperLoader, and realize the hot loading function of JSP file by establishing a new JSP class loader.
Does the loading mechanism of tomcat violate the parental delegation model recommended by java? The answer is: Yes.
Obviously, tomcat is not implemented in this way. In order to achieve isolation, tomcat does not abide by this Convention. Each webappClassLoader loads the class files in its own directory and will not pass them to the parent class loader, breaking the parental delegation mechanism.
Simulate the webappClassLoader of Tomcat to load its own war package. Different versions of classes in the application can coexist and isolate each other
public class MyClassLoaderTest { static class MyClassLoader extends ClassLoader { private String classPath; public MyClassLoader(String classPath) { this.classPath = classPath; } 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(); return data; } 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(); } } /** * Rewrite the class loading method to implement its own loading logic and do not delegate it to parents * @param name * @param resolve * @return * @throws ClassNotFoundException */ 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) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); //Non custom classes are loaded by parental delegation if (!name.startsWith("com.tuling.jvm")){ c = this.getParent().loadClass(name); }else{ c = findClass(name); } // this is the defining class loader; record the stats sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } if (resolve) { resolveClass(c); } return c; } } } public static void main(String args[]) throws Exception { MyClassLoader classLoader = new MyClassLoader("D:/test"); Class clazz = classLoader.loadClass("com.tuling.jvm.User1"); Object obj = clazz.newInstance(); Method method= clazz.getDeclaredMethod("sout", null); method.invoke(obj, null); System.out.println(clazz.getClassLoader()); System.out.println(); MyClassLoader classLoader1 = new MyClassLoader("D:/test1"); Class clazz1 = classLoader1.loadClass("com.tuling.jvm.User1"); Object obj1 = clazz1.newInstance(); Method method1= clazz1.getDeclaredMethod("sout", null); method1.invoke(obj1, null); System.out.println(clazz1.getClassLoader()); } } Operation results: =======Load the class and call the method with your own loader======= com.tuling.jvm.MyClassLoaderTest$MyClassLoader@266474c2 =======Another one User1 Version: load the class and call the method with your own loader======= com.tuling.jvm.MyClassLoaderTest$MyClassLoader@66d3c617
==Note = =: in the same JVM, two class objects with the same package name and class name can coexist. Because their class loaders can be different, it depends on whether the two class objects are the same. In addition to whether the package name and class name of the class are the same, their class loaders also need to be the same in order to think they are the same.
Simulate the hot loading of JasperLoader of Tomcat
Principle: the background startup thread listens for changes in jsp files. If changes occur, find the loader reference (gcroot) of the servlet class corresponding to the jsp, regenerate a new JasperLoader loader, assign a value to the reference, and then load the servlet class corresponding to the new jsp. The previous loader will be destroyed at the next gc because there is no gcroot reference.
The following User class code is attached:
package com.tuling.jvm; public class User { private int id; private String name; public User() { } public User(int id, String name) { super(); this.id = id; this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public void sout() { System.out.println("=======Load the class and call the method with your own loader======="); } }
Supplement: the Hotspot source JVM starts the process of executing the main method
This article is composed of blog one article multi posting platform OpenWrite release!