Deep into the jvm kernel - Principle, diagnosis and Optimization - 6. Class loader

Keywords: Programming Java JDBC MySQL jvm

  1. class Loading Verification Process

    1. Load

       The first stage of loading classes
       Get the binary stream of the class
       Conversion to method area data structure
       Generate corresponding java.lang.Class objects in the Java heap
      
      
    2. link

      1. Verification

      2. Get ready

      3. analysis

    3. Initialization

       Execution class constructor < clinit >
       static variable assignment statement
       static {} statement
       Make sure that < clinit > of the parent class is invoked before < clinit > of the subclass is invoked
       < clinit > is thread-safe
      
      
  2. What is ClassLoader

     ClassLoader is an abstract class
     An instance of ClassLoader will read into Java bytecode and load the class into the JVM
     ClassLoader can be customized to meet different bytecode stream acquisition methods
     ClassLoader is responsible for the loading phase of the class loading process
    
    
  3. ClassLoader default design pattern in JDK

    1. Method

       The Important Method of ClassLoader
       public Class<?> loadClass(String name) throws ClassNotFoundException
       Load and return a Class
       protected final Class<?> defineClass(byte[] b, int off, int len)
       Define a class that is not called publicly
       protected Class<?> findClass(String name) throws ClassNotFoundException
       The loadClass callback method customizes the recommended practices of ClassLoader
       protected final Class<?> findLoadedClass(String name) 
       Find classes that have been loaded
      
      
    2. classification

       BootStrap ClassLoader (Start ClassLoader)
       Extension Class Loader
       App ClassLoader (Applying ClassLoader/System ClassLoader)
       Custom Class Loader
      
       Each ClassLoader has a Parent as its father
      
      
    3. Cooperative work

      1. Loading order of test classes

        	public class FindClassOrder {
        	public static void main(String args[]){
        	HelloLoader loader=new HelloLoader();
        	loader.print();
        	}
        	}
        
        
        	public class HelloLoader {
        	public void print(){
        		System.out.println("I am in apploader");
        	}
        	}
        
        

        Run the above code directly: I am in apploader

        	Add the parameter - Xbootclasspath/a:/Users/heliming/IdeaProjects/democloud/jvm/src/main/java
        
        
        	//Compile the class file of this java file into the / Users/heliming/IdeaProjects/democloud/jvm/src/main/java directory
        	public class HelloLoader {
        	public void print(){
        		System.out.println("I am in bootloader");
        	}
        	}
        
        

        	I am in bootloader
        	HelloLoader will not be loaded in AppLoader at this time
        	I am in apploader is not loaded in classpath
        	Explain that class loading is from top to bottom
        
        
      2. When testing to find classes, it's from the bottom up

        Force loading in apploader

        	/**
        	 * description: https://www.cnblogs.com/cl-rr/p/9081817.html defineClass()Method is more used to load files that are no longer classes, or to overwrite the bytecode of the original class when AOP. It should be noted that for the same class, use defineClass() twice or more to throw an exception.
        	 *
        	 * @author: dawn.he QQ:       905845006
        	 * @email: dawn.he@cloudwise.com
        	 * @email: 905845006@qq.com
        	 * @date: 2019/9/24    6:22 PM
        	 */
        	//package com.zejian.classloader;
        
        	import java.io.*;
        	import java.lang.reflect.Method;
        
        	/**
        	 * Created by zejian on 2017/6/21.
        	 * Blog : http://blog.csdn.net/javazejian [The original address, please respect the original]
        	 */
        	public class FileClassLoader extends ClassLoader {
        		private long lastTime;
        		private String rootDir;
        
        		public FileClassLoader(String rootDir) {
        			this.rootDir = rootDir;
        		}
        
        		/**
        		 * Writing the logic of findClass method
        		 * @param name
        		 * @return
        		 * @throws ClassNotFoundException
        		 */
        		@Override
        		protected Class<?> findClass(String name) throws ClassNotFoundException {
        			// Gets the class file byte array of the class
        			byte[] classData = getClassData(name);
        			if (classData == null) {
        				throw new ClassNotFoundException();
        			} else {
        				//Generating class objects directly
        				return defineClass(name, classData, 0, classData.length);
        			}
        		}
        
        		/**
        		 * Write the logic to get the class file and convert it to a bytecode stream
        		 * @param className
        		 * @return
        		 */
        		private byte[] getClassData(String className) {
        			// Read the bytes of the class file
        			String path = classNameToPath(className);
        			try {
        				InputStream ins = new FileInputStream(path);
        				ByteArrayOutputStream baos = new ByteArrayOutputStream();
        				int bufferSize = 4096;
        				byte[] buffer = new byte[bufferSize];
        				int bytesNumRead = 0;
        				// Read the bytecode of the class file
        				while ((bytesNumRead = ins.read(buffer)) != -1) {
        					baos.write(buffer, 0, bytesNumRead);
        				}
        				return baos.toByteArray();
        			} catch (IOException e) {
        				e.printStackTrace();
        			}
        			return null;
        		}
        
        		/**
        		 * Full path of class file
        		 * @param className
        		 * @return
        		 */
        		private String classNameToPath(String className) {
        			return rootDir + File.separatorChar
        					+ className.replace('.', File.separatorChar) + ".class";
        		}
        
        		public static void main(String[] args) throws ClassNotFoundException {
        	//        String rootDir="/Users/zejian/Downloads/Java8_Action/src/main/java/";
        			String rootDir="/Users/heliming/IdeaProjects/democloud/jvm/target/classes/";
        
        			//Create a custom file class loader
        			FileClassLoader loader = new FileClassLoader(rootDir);
        
        			try {
        				//Load the specified class file
        	//            Class<?> object1=loader.loadClass("com.zejian.classloader.DemoObj");
        				Class<?> object1=loader.loadClass("HelloLoader");
        				Object o = object1.newInstance();
        				Method method=o.getClass().getDeclaredMethod("print", null);
        				method.invoke(o, null);
        				//Output: I am in apploader
        			} catch (Exception e) {
        				e.printStackTrace();
        			}
        		}
        	}
        
        

        Printing: I am in apploader

        When looking for classes, first look at the underlying Loader, from bottom to top. If Apploader can find it, it won't go to the upper loader to load it.

      3. Replace the main function above, and test findClass can only be loaded once

        	public static void main(String[] args) throws ClassNotFoundException {
        			String rootDir = "/Users/heliming/IdeaProjects/democloud/jvm/target/classes/";
        			//Create a custom file class loader
        			FileClassLoader loader = new FileClassLoader(rootDir);
        			FileClassLoader loader2 = new FileClassLoader(rootDir);
        
        			try {
        
        				Class<?> object1 = loader.loadClass("HelloLoader");
        
        				Object o = object1.newInstance();
        				Method method = o.getClass().getDeclaredMethod("print", null);
        				method.invoke(o, null);
        				Class<?> object2 = loader2.loadClass("HelloLoader");
        				o = object1.newInstance();
        				method = o.getClass().getDeclaredMethod("print", null);
        				method.invoke(o, null);
        				System.out.println("loadClass->obj1:" + object1.hashCode());
        				System.out.println("loadClass->obj2:" + object2.hashCode());
        
        				//Load the specified class file, call findClass(), bypass the detection mechanism, and create different class objects.
        				Class<?> object3 = loader.findClass("HelloLoader");
        
        				//findClass can only be loaded once. If it is reloaded again, it will cause an error to reload the class.
        				//Class<?> object5 = loader.findClass("HelloLoader");
        
        
        				Class<?> object4 = loader2.findClass("HelloLoader");
        
        				System.out.println("loadClass->obj3:" + object3.hashCode());
        				System.out.println("loadClass->obj4:" + object4.hashCode());
        				/**
        				 * Output results:
        				 * loadClass->obj1:644117698
        				 loadClass->obj2:644117698
        				 findClass->obj3:723074861
        				 findClass->obj4:895328852
        				 */
        
        			} catch (Exception e) {
        				e.printStackTrace();
        			}
        		}
        
    4. The Problem of Parent Delegation Model

    5. Solve:

      	Thread. setContextClassLoader()
      	Context loader
      	It's a role.
      	To solve the problem that the top-level ClassLoader cannot access the classes of the bottom-level ClassLoader
      	The basic idea is that in the top-level ClassLoader, an instance of the underlying ClassLoader is passed in.
      
      

      It can be seen from the graph that rt.jar core package is loaded by Bootstrap class loader. It contains SPI core interface classes. Because classes in SPI often need to call methods of external implementation classes, while jdbc.jar contains external implementation classes (jdbc.jar exists in classpath path path path) which cannot be loaded by Bootstrap class loader, it can only delegate threads. The following class loader loads the implementation classes in jdbc. jar into memory for SPI-related classes to use. Obviously, this method of loading thread context class loader destroys the "Parent Delegation Model". It abandons the Parent Delegation Load Chain mode in the execution process, so that the program can use the class loader in reverse, which also makes the Java class loader more flexible. To further validate this scenario, look at the source code of the DriverManager class, which is a class in the Java core rt.jar package that manages the implementation drivers of different databases, namely Driver, which implements the java.sql.Driver interface in the Java core package, such as com.mysql.jdbc.Driver in the MySQL driver package, here Mainly see how to load the external implementation class. The following code will be executed during drivermanager initialization

      	//DriverManager is a class of the Java core package rt.jar
      	public class DriverManager {
      		//Eliminate unnecessary code
      		static {
      			loadInitialDrivers();//Execute this method
      			println("JDBC DriverManager initialized");
      		}
      
      	//Load Initial Drivers Method
      	 private static void loadInitialDrivers() {
      		 sun.misc.Providers()
      		 AccessController.doPrivileged(new PrivilegedAction<Void>() {
      				public Void run() {
      					//Loading external Driver implementation classes
      					ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
      				  //Eliminate unnecessary code...
      				}
      			});
      		}
      

      The loadInitialDrivers() method is executed when the DriverManager class is initialized. In this method, the driver class is loaded by ServiceLoader.load(Driver.class); to load the driver class implemented externally, the ServiceLoader class reads the contents of META-INF file under mysql's jdbc.jar, as shown below.

      The inheritance classes of com.mysql.jdbc.Driver are as follows:

      	public class Driver extends com.mysql.cj.jdbc.Driver {
      		public Driver() throws SQLException {
      			super();
      		}
      
      		static {
      			System.err.println("Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. "
      					+ "The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.");
      		}
      	}
      

      As can be seen from the comments, the usual use of com.mysql.jdbc.Driver has been discarded and replaced by com.mysql.cj.jdbc.Driver, which means that the authorities no longer recommend that we register MySQL drivers with the following code

      	//Registering driver classes in this way is not recommended
      	Class.forName("com.mysql.jdbc.Driver");
      	String url = "jdbc:mysql://localhost:3306/cm-storylocker?characterEncoding=UTF-8";
      	// Getting database connections through java Libraries
      	Connection conn = java.sql.DriverManager.getConnection(url, "root", "root@555");
      
      

      Instead, the registration steps are removed directly, as follows

      	String url = "jdbc:mysql://localhost:3306/cm-storylocker?characterEncoding=UTF-8";
      	// Getting database connections through java Libraries
      	Connection conn = java.sql.DriverManager.getConnection(url, "root", "root@555");
      
      

      This way ServiceLoader will help us deal with everything and ultimately load through the load() method to see how the load() method is implemented.

      	public static <S> ServiceLoader<S> load(Class<S> service) {
      		 //Loading through the thread context class loader
      		  ClassLoader cl = Thread.currentThread().getContextClassLoader();
      		  return ServiceLoader.load(service, cl);
      	  }
      

      Obviously, it is loaded by thread context class loader. In fact, the SPI class of core package loads external implementation class based on thread context class loader. In this way, Java core code calls external implementation class internally. We know that the thread context class loader is AppClassLoader by default, so why not get the class loader directly through getSystem ClassLoader () to load classes under the classpath path path path path? Actually, it's feasible, but one drawback of using getSystemClassLoader() directly to get AppClassLoader loaded classes is that there will be problems when code is deployed to different services, such as Java Web application services or services such as EJB, because these services use thread contexts. Class loaders are not AppClassLoaders, but Java Web applications are their own class loaders. Class loaders are different. So we should use less getSystem ClassLoader (). In short, different services may use different default ClassLoaders, but using thread context class loaders can always get the same ClassLoader as the current program execution, thus avoiding unnecessary problems. ok ~. Talk about the thread context class loader for the moment. The DeriveManager class described above, you can look at the source code by yourselves, I believe you will have more experience. In addition, this article does not elaborate too much about Service Loader. After all, our topic is class loader, but Service Loader is a very good decoupling mechanism. Home can refer to its relevant usage.

  4. Breaking the Conventional Model and Hot Replacement

    Destruction of Parental Patterns
    	Parent mode is the default mode, but it is not necessary to do so.
    	Tomcat's Webapp Class Loader loads its own Class first, fails to find it, and then delegates parent.
    	OSGi's lassLoader forms a mesh structure that freely loads Class as needed
    

    In the java directory

    	javac Worker.java
    
    Start main function 
    Modify Worker.java
     Again, javac Worker.java
     Output changed
    

    HelloMain.java

    import java.io.File;
    import java.lang.reflect.Method;
    import java.net.URL;
    import java.net.URLClassLoader;
    
    public class HelloMain {
    	private URLClassLoader classLoader;
    	private Object worker;
    	private long lastTime;
    //	private String classDir="/Users/heliming/IdeaProjects/democloud/jvm/target/classes/";
    	private String classDir="/Users/heliming/IdeaProjects/democloud/jvm/src/main/java/";
    	public static void main(String[] args) throws Exception {
    		HelloMain helloMain=new HelloMain();
    		helloMain.execute();
    	}
    
    	private void execute() throws Exception {
    		while(true){
    			//Monitor whether loading is required
    			if(checkIsNeedLoad()){
    				System.out.println("New version detected, ready to reload");
    				reload();
    				System.out.println("Reload completed");
    			}
    			//One second
    			invokeMethod();
    			Thread.sleep(1000);
    
    		}
    	}
    
    	private void invokeMethod() throws Exception {
    		//Called by reflection
    		//The main reason for using reflection is that you don't want Work to be loaded by appclassloader.
    //		If loaded by app classloader, loading through custom loader can be a bit problematic
    		Method method=worker.getClass().getDeclaredMethod("sayHello", null);
    		method.invoke(worker, null);
    	}
    
    	private void reload() throws Exception {
    		classLoader = new MyClassLoader(new URL[] { new URL(
    				"file:"+classDir)});
    		worker =  classLoader.loadClass("Worker")
    				.newInstance();
    		System.out.println(worker.getClass());
    
    	}
    
    	private boolean checkIsNeedLoad() {
    		File file=new File(classDir+ "Worker.class");
    		long newTime=file.lastModified();
    		if(lastTime<newTime){
    			lastTime=newTime;
    			return true;
    		}
    		return false;
    	}
    
    
    }
    

    Worker.java

    public class Worker {
    	public void sayHello(){
    		System.out.println("version: fds");
    	}
    }
    

    MyClassLoader.java

    import java.net.URL;
    import java.net.URLClassLoader;
    
    public class MyClassLoader extends URLClassLoader {
    
    	public MyClassLoader(URL[] urls) {
    		super(urls);
    	}
    
    	// Break parental mode to ensure that your classes are loaded by your classloader
    
    	@Override
    	protected synchronized Class<?> loadClass(String name, boolean resolve)
    			throws ClassNotFoundException {
    		Class c = findLoadedClass(name);
    		if (c == null) {
    			try {
    				//Here, if you load the object class first and you can't find it, you will get an error, so under catch
    				c=findClass(name);
    			} catch (Exception e) {
    			}
    		}
    		if(c==null){
    			c=super.loadClass(name, resolve);
    		}
    		return c;
    	}
    }
    
    

Posted by danf_1979 on Wed, 25 Sep 2019 00:07:43 -0700