How is our code loaded into the JVM for execution? (a)

We looked at common class loaders and how they work, but we can also customize class loaders to understand more.

Let’s have a custom class loader

Main steps:

  1. Inherit this
  2. Override the findClass() method
public class MyClassLoader extends ClassLoader { private String path; public MyClassLoader(String path) { ClassLoader parent = getParent(); System.out.println("parent == " + parent); this.path = path; } @Override protected Class<? > findClass(String name) throws ClassNotFoundException { try { byte[] result = getClass(name); if (result == null) { throw new ClassNotFoundException(); } else {return defineClass(name, result, 0, result.length); } } catch (Exception e) { e.printStackTrace(); } return super.findClass(name); } private byte[] getClass(String name) { try { if (Objects.isNull(name) || name.length() == 0) return null; int index = name.lastIndexOf("."); String path = this.path; if(index == -1){ path = path + name + ".class"; }else{ path = path + name.substring(index+1) + ".class"; } return Files.readAllBytes(Paths.get(path)); } catch (Exception e) { e.printStackTrace(); } return null; }} // Put the generated study. class file in the directory of drive E // then delete the study. Java and study. class in the project --> consider the Study class as a non-project code file // through the local path, Load the study. class file outside the project public class JVMTest {public static void main(String[] args) throws Exception {MyClassLoader loader1 = new MyClassLoader("E:/"); Class<? > clazz1 = loader1.findClass("com.tomato.Study"); Study o = (Study)clazz1.newInstance(); }}Copy the code

Even if a Class object is loaded from the same Class file, the JVM may think it is two different classes

ClassCastException if you do not delete study. Java from the project, ClassCastException will be reported.

When determining whether two classes are identical, the JVM determines not only whether they have the same class name, but also whether they are loaded by the same classloader instance. The JVM considers two classes to be the same only if they both satisfy. Even if two classes are the same class bytecode, if they are loaded by two different ClassLoader instances, the JVM will treat them as two different classes. Such as a Java class ClassLoaderSimple org.classloader.simple.Net on the network, after the javac compiler to generate the bytecode file NetClassLoaderSimple. Class, ClassLoaderA and ClassLoaderB these two class loaders and read the NetClassLoaderSimple. Class files, and define the Java respectively. Lang. To represent the class, the class instance for the JVM, they are two different instance objects, They are really the same bytecode file, if you try to put this Class instance to generate specific object transform, Java runtime exception will be thrown. Lang. ClassCaseException, suggest they are two different types.

Now that we have custom classloaders, when does the JVM load the Class file, other than in the above scenario where we manually load a Class? Are all the Class files loaded? If you load it all in, the startup speed will be affected, so how to optimize it?

Class loading When does the Class file load?

Instead of loading all the classes into memory, the project is LazyLoading as needed. There are five cases where classes must be loaded.

  1. The new getstatic putstatic Invokestatic directive, except to access final, which is determined when compiled into a class file
  2. When java.lang.reflect is called on class reflection
  3. The initial subclass is, the parent class must be loaded first
  4. When a VM starts, the main class to be executed must be loaded
  5. Dynamic language support Java. Lang. Invoke. Analytical results for MethodHandle REF_getstatic, REF_putstatic, REF_invokestatic method handles, this class is initialized.

Now that we know when a Class loader loads a Class file, here are three execution modes for loading.

Three execution modes for the JVM

Java -> 2. Javac -> 3. XXX.Class -> 4.ClassLoader -> 5. Bytecode interpreter, JIT -> 6. Execution engine execution

The fifth step involves the execution mode, which can be changed by tweaking the JVM (default mixed mode)

  • Explain pattern

The Bytecode Interpreter interprets execution, which is fast to start but slow to execute because no compilation is required, and can be specified as pure interpretation mode with the -xin argument.

  • Compilation mode

Jit-compiled to native code (implemented in C), fast but slow to start, and can be specified in pure compiled mode with the -xcomp argument.

  • Mixed mode

Since both methods are good in their own right, the JVM uses both, plus “HotSpot code compilation” to call it a hybrid mode, starting with interpreted execution and then using HotSpot code detection to determine if the code is HotSpot code and if so, compiling it. You can specify mixed mode with the -xmixed argument.

In mixed mode, the JVM detects hot code to determine whether it needs to be compiled (optimized) :

  1. Methods that are called multiple times (method counters: monitor method execution frequency);
  2. Loops that are called multiple times (loop counters: monitor how often loops execute);

Very special “context classloader”

Common class loaders introduced earlier: Bootstrap ClassLoader,Extention ClassLoader,App ClassLoader,Customer ClassLoader all exist, but the “context ClassLoader” is a definition. It could be any of the above.

Set the context class loader is belong to the method of Thread Thread setContextClassLoader (this cl)

Official explanation:

/** * Sets the context ClassLoader for this Thread. The context * ClassLoader can be set when a thread is created, and allows * the creator of the thread to provide the appropriate class loader, * through {@code getContextClassLoader}, To code running in the thread * when loading classes and resources. * Set context class loaders for this thread. This context classloader can be set (specified) at thread creation time and also allows the thread creator to provide an appropriate classloader to load classes * or resources in the running thread via the getContextClassLoader method. * */ public void setContextClassLoader(ClassLoader cl) { SecurityManager sm = System.getSecurityManager(); if (sm ! = null) { sm.checkPermission(new RuntimePermission("setContextClassLoader")); } contextClassLoader = cl; }Copy the code

The setContextClassLoader() method can be used to specify the class loader, or if not, use the class loader where the thread is currently created. Since the Thread of the current new Thread() is also the creator of the current Thread, please let me know in the comments if you have a different idea.

What do you use a context classloader for? Break the parental delegation mechanism

“Even if the same Class file to load due to the Class object, the JVM may also be considered to be two different Class” reasons, there are so many different good parents delegate mechanism, but as a (men cheat philandering feelings) aggressive developers don’t meet the mechanism of “parent” arrangement, developers need is custom rights (freedom). At the same time, we will find that many core classes are loaded by Bootstrap ClassLoader, but their implementation may be handed over to other vendors. For example, the core code of Java’s Service Provider Interface (SPI) such as JNDI and JDBC is loaded by Bootstrap ClassLoader, but the implementation of JNDI and JDBC is provided by a third party. In this case, you can use this mechanism to “break” the parent delegation mechanism, effectively breaking the restrictions imposed by the parent delegation mechanism.

Use the custom class loader to complete the hot plug function

With all this theoretical knowledge, what functions can we use it to accomplish? (Ten years to sharpen the gun, not for the gun, is it for the Unicorn arm)

The following takes you to complete a simple server hot-plug function: modify the new code does not need to recompile the entire project, deployment and other operations, as long as the new code will be uploaded to the server, you can run the latest code.

Implementation steps

  1. Build the Springboot project
  2. New NetHotSwapClassLoader. Java — — network hotplug class loader
  3. Prepare two different versions of xxxservice.java — service classes for verifying hot plug functionality
  4. New hotswapController.java — upload the Class file and eventually call the execution code in the Class file

PS: In order to make students better understand the whole process and deepen the knowledge, this hot plug function has been written in two versions

  • Hotplug NetHotSwapClassLoader. Java — — network class loader
Public class NetHotSwapClassLoader extends ClassLoader {/** * constructor */ public NetHotSwapClassLoader() {// make the loading of NetHotSwapClassLoader the same as the parent loading of other classes by NetHotSwapClassLoader. To ensure consistent super class loader (NetHotSwapClassLoader. Class. GetClassLoader ()); } /** * Load Class * @param bytes Class file byte stream * @return Class object * @throws ClassNotFoundException exception */ public Class<? > loadClassBytes(byte[] bytes) throws ClassNotFoundException { return defineClass(null, bytes, 0, bytes.length); }}Copy the code
  • Xxxservice.java — the first version of the code
Public class XXXService {public static Object processStatic() {return processStatic:Hello  World!" ; } public Object process() {return "process:Hello World!" ; }}Copy the code
  • HotSwapController.java
@RestController @RequestMapping("/hotSwap") public class HotSwapController { @PostMapping(value = "process") public Object process(@RequestPart("file") MultipartFile[] multipartFiles) { try { byte[] bytes = multipartFiles[0].getBytes();  NetHotSwapClassLoader classLoader = new NetHotSwapClassLoader(); // Load Class<? > clazz = classLoader.loadClassBytes(bytes); Object instance = clazz.newInstance(); Clazz.getdeclaredmethod ("processStatic"); // Execute the code in the uploaded class file (not in the current project). Method method2 = clazz.getDeclaredMethod("process"); Object invoke1 = method1.invoke(null); Object invoke2 = method2.invoke(instance); System.out.println("processStatic execution result == "+ invoke1); System.out.println("process execution result == "+ invoke2); return invoke1; } catch (Exception e) { e.printStackTrace(); } return "exception occurs, not executed as expected "; } @getMapping (value = "test") public Object test() {XXXService hotSwapService = new XXXService(); Object processStatic = XXXService.processStatic(); Object process = hotSwapService.process(); System.out.println("test processStatic == "+ processStatic); System.out.println("test process result == "+ process); return processStatic; }}Copy the code

To prepare for this step, we will now test the code we are executing by calling /hotSwap/test via Postman. The result is as follows:

Test processStatic Execution result == processStatic:Hello World! Test process Execution result == process:Hello World!Copy the code

We have now seen the first version of the code on the line, so we are executing the second version without redeploying the second version: now prepare the second version of the code

  • Xxxservice.java — the code of the second version (the second version of the code can be written by myself, not necessarily by me, as long as there are differences between the two versions of the code)
Public class XXXService {public Static Object processStatic() {SbProperties SbProperties bean generated by SpringBoot  = SpringContextHolder.getApplicationContext().getBean(SbProperties.class); return bean; } public Object process() {// The second version of the code generated by SpringBoot HelloService HelloService = SpringContextHolder.getApplicationContext().getBean(HelloService.class); return helloService.test1(); }}Copy the code

Compile the second version of the code to get xxxservice. class file and upload it to interface /hotSwap/process using Postman.

The execution results are as follows:

ProcessStatic Run result == SbProperties(title= learn, desc= day up) process run result == service test1Copy the code

If you call the /hotSwap/test interface again, you will find that you are still executing the first version of the code, Why? We can recall that a Class that is not loaded by the same ClassLoader is still considered by the JVM to be not the same Class, so if you want to call /hotSwap/test, which is the latest version 2 code, Ensure that both classes are loaded by the same ClassLoader and that the second version of the Class object is flushed into memory so that the global Class object can take effect.

Let’s implement version 2.

1. Create a new interface named YService, and define the same method as XXXService. 2. Create a new interface named YService, and define the same method as XXXService. Then let XXXService inherit it. The second version of the code does not inherit the Class exported by YService, so you need to re-implement YService and re-export the second version of the code!! This step is important, otherwise upload the previous Class file will cause problems))

public interface YService { Object process(); Public class XXXService Implement YService {public static Object processStatic() {// first version of code return "processStatic:Hello World!" ; } @override public Object process() {return "process:Hello World!" ; Public class XXXService Implement {public static Object processStatic() {// second version of the code return "Good Good Study!" ; } @override public Object process() {return "Day Day UP!" ; }}Copy the code

2. Modification NetHotSwapClassLoader

Public class NetHotSwapClassLoader extends ClassLoader {public static final Map<String, byte[]> classBytes = new HashMap<String, byte[]>(); public static final Map<String,Class<? >> classMap = new HashMap<String, Class<? > > (); /** * constructor */ public NetHotSwapClassLoader() { To ensure consistent super class loader (NetHotSwapClassLoader. Class. GetClassLoader ()); } /** * Since most of the time we use objects from the core library, such as Object and String objects, * If we are not careful when overwriting, we can probably throw a ClassNotFoundException because according to the ClassPath, These libraries are loaded by Bootstrap ClassLoader. * The custom ClassLoader loads a ClassPath that does not include this region at all. */ // @Override // public Class<? > loadClass(String name) throws ClassNotFoundException { // return super.loadClass(name); // } @Override protected Class<? > findClass(String name) throws ClassNotFoundException {// Break the parent delegate mechanism without finding byte[] buf = classbytes.get (name); if (buf == null) { return Class.forName(name); } if(classMap.containsKey(name)){ return classMap.get(name); } Class<? > aClass = defineClass(name, buf, 0, buf.length); return aClass; } /** * Load Class * @param bytes Class file byte stream * @return Class object * @throws ClassNotFoundException exception */ public Class<? > loadClassBytes(String name,byte[] bytes) throws ClassNotFoundException { return defineClass(name, bytes, 0, bytes.length); }}Copy the code
  1. HotSwapController.java
@RestController @RequestMapping("/hotSwap") public class HotSwapController { @PostMapping(value = "process") public Object process(@RequestPart("file") MultipartFile[] multipartFiles) { try { byte[] bytes = multipartFiles[0].getBytes();  NetHotSwapClassLoader.classBytes.put(XXXService.class.getName(),bytes); NetHotSwapClassLoader classLoader = new NetHotSwapClassLoader(); // Load Class<? > clazz = classLoader.findClass(XXXService.class.getName()); Object instance = clazz.newInstance(); Clazz.getdeclaredmethod ("processStatic"); // Execute the code in the uploaded class file (not in the current project). Method method2 = clazz.getDeclaredMethod("process"); Object invoke1 = method1.invoke(null); Object invoke2 = method2.invoke(instance); System.out.println("processStatic execution result == "+ invoke1); System.out.println("process execution result == "+ invoke2); return invoke1; } catch (Exception e) { e.printStackTrace(); } return "exception occurs, not executed as expected "; } @GetMapping(value = "test") public Object test() { Object processStatic = null; NetHotSwapClassLoader classLoader = new NetHotSwapClassLoader(); FindClass <? LoadClass <? LoadClass <? LoadClass <? > clazz = classLoader.findClass(XXXService.class.getName()); YService hotSwapService = (YService) clazz.newInstance(); processStatic = hotSwapService.process(); } catch (Exception e) { e.printStackTrace(); } return processStatic; }}Copy the code

After uploading the Class file, execute the test() method again and find that the latest code is executed. Because there is no encapsulation, it looks redundant and can be extracted and written as a utility class, which makes it a little easier to look at.

At this point, by understanding the ClassLoader, you have completed a simple hot-pluggable function.

Hope that through the comparison of the two versions, hope to let students deepen their understanding of Classloader.