We know that the Java language is compiled once and runs on multiple platforms. This is because Java is designed to compile and run as separate processes. Compilation is responsible for compiling source code into bytecodes that the JVM can recognize, loading bytecodes at run time, and interpreting them as machine instructions.

Because source code is compiled into bytecode, the JVM platform in addition to the Java language, groovy, Scala, etc. Because the bytecode is loaded to run, so there are APM, custom classloader, dynamic language and other technologies. Make up the rich Java world.

Javac compilation process

  1. Parse: Reads data. Java source file, do LEXER and PARSER
  2. Enter: Generates a symbol table
  3. Process: Process annotations
  4. Attr: Check semantic legitimacy, constant folding
  5. Flow: Data flow analysis
  6. Desugar: Remove grammatical sugar
  7. Generate: generates bytecode

The main purpose of compile time is to compile Java source code into bytecode that conforms to the JVM specification. At runtime, bytecode is loaded and executed by the JVM, and the program runs.

The Java language is not bound to the JVM. Any bytecode that conforms to the JVM specification can be executed, but the bytecode is not necessarily compiled from the Java language. As a result, groovy, Scala, Kotlin and many other languages have emerged on the JVM platform.

If you’re interested, you can also run your favorite language on the JVM.

Class life cycle

  1. Loading: Loading. Is the first stage, mainly load bytecode, static storage structure into method area data structure, generation of class objects. There is no limit to the source of the bytecode; it can be a file, ZIP, web, JSP, or even an encrypted file. This stage allows you to implement custom behavior using a custom ClassLoader, which opens up many possibilities for bytecode gameplay.
  2. “Verification” : Ensure bytecode compliance with JVM specifications.
  3. -Lily: Preparation. Formally set initial values for variables defined in a class.
  4. The resolution is: The process of replacing symbolic references in a constant pool with direct references.
  5. Initialization: Initialization. This puts control of the program in the hands of the application, which executes ·() and constructors.
  6. Using: Using. With the initialized class, you’re in the realm of application logic.
  7. 2. Unloading. You need to satisfy that all instances of the Class have been GC, that the ClassLoader that loaded the Class has been GC, and that the java.lang.Class object of the Class has not been referenced. Each JSP is a separate Classloader. When the JSP changes, the old classloader is uninstalled and a new classloader is created to load the JSP, thus implementing hot loading.

Before the Initialization phase, only the loading phase can add custom logic through the custom Classloader. The rest of the loading phase is done by the JVM. That’s the point of this article, what a Classloader can do.

Parents delegate

Before we can understand what classLoaders can do, we must first understand the parent delegate model. Java is known to be single-inherited, and ClassLoaders inherit this design philosophy.

JDK 8, JDK9 after the introduction of module functions, classloader inheritance relationship has changed.

From the perspective of the JVM, there are only two kinds of loaders. One is Bootstrap classloader, which is implemented by C++ or Java. The other is another classloader. Both are written in the Java language and inherit from the java.lang.ClassLoader abstract class.

  1. The Application of this. Responsible for loading classes in the user’s path, this is the default class loader if there is no custom class loader.
  2. The Extension of this. Is responsible for loading all libraries in the path specified by the

    \lib\ext, or java.ext.dirs system variable.
  3. The BootStrap this. Responsible for loading the classes specified by the

    \lib, -xbootCLASspath parameter. The application cannot obtain the Classloader, and uses null instead.

ClassLoader application case

The above is the background, the following is the highlight. Now that you know the JavAC compilation process, the class lifecycle, and the ClassLoader parent delegate, what you can do with it.

After learning about the “life cycle of a class”, we know that the ClassLoader can customize bytecode only in the loading phase. The other phases are implemented by the JVM. Let me take a look at a few application scenarios, intuitive feeling.

Applications in the Java SPI

The Java Service Provider Interface (SPI) is a mechanism for dynamically loading services. You can implement your own SPI by following the rules and load the service using ServiceLoader.

Components of the Java SPI:

  1. Service interface: An interface or abstract class defines service functionality.
  2. Service provider: The implementation of a service interface that provides a specific service.
  3. Configuration file: You need to place a file with the same service interface name in the meta-INF /services directory, with each line containing the full class name of an implementation class.
  4. ServiceLoader: The main class of the Java SPI, used to load service implementations through service interfaces. There are a number of utility methods for reloading services.

Java SPI Example

Implement an SPI and load the service using ServiceLoader.

  1. Defining service Interfaces
public interface MessageServiceProvider {
	void sendMessage(String message);
}
Copy the code
  1. Define the service interface to realize email and push message connection.
public class EmailServiceProvider implements MessageServiceProvider {
	public void sendMessage(String message) {
		System.out.println("Sending Email with Message = "+message);
	}
}
public class PushNotificationServiceProvider implements MessageServiceProvider {
	public void sendMessage(String message) {
		System.out.println("Sending Push Notification with Message = "+message); }}Copy the code
  1. Write service configuration in the meta-inf/services create util. Spi. MessageServiceProvider files, content is the full path of the service industry
util.spi.EmailServiceProvider
util.spi.PushNotificationServiceProvider
Copy the code
  1. Finally, use the ServiceLoader to load and test the service.
public class ServiceLoaderTest {
  public static void main(String[] args) {
    ServiceLoader<MessageServiceProvider> serviceLoader = ServiceLoader
        .load(MessageServiceProvider.class);
    for (MessageServiceProvider service : serviceLoader) {
      service.sendMessage("Hello"); }}Copy the code

The output is as follows:

Sending Email with Message = Hello
Sending Push Notification with Message = Hello
Copy the code

Here is the project file structure:

Java SPI Class Loader

The ServiceLoader class is in the rt.jar package and should be loaded by the Bootstrap Classloader, while the EmailServiceProvider class is the one I defined and should be loaded by the Application Classloader. Let’s test this idea.

ServiceLoader<MessageServiceProvider> serviceLoader = ServiceLoader.load(MessageServiceProvider.class);
System.out.println(ServiceLoader.class.getClassLoader());

for (MessageServiceProvider service : serviceLoader) {
System.out.println(service.getClass().getClassLoader());
}
Copy the code

The results are as follows:

// The ServiceLoader is loaded by Bootstrap Classloader. Can't get this null / / by Application load JDK. This internal. Loader. The ClassLoaders$AppClassLoader@3fee733d
jdk.internal.loader.ClassLoaders$AppClassLoader@3fee733d
Copy the code

Bootstrap ClassLoader cannot load application classes according to the classloader inheritance relationship. How does the ServiceLoader reference the SPI service?

  1. It is contextClassLoader for the main thread, which is contextClassLoader set by the JVM. With this thread, it is assumed that the ServiceLoader loads the service through the contextClassLoader.
  2. ② is the service to load.

  1. You can see from the call stack that the ServiceLoader iterator loads the service lazily.
  2. ① Is the Application Classloader, obtained from the thread context.
  3. ② A service implementation that uses thread contextClassLoader to load bypasses the parent delegate.

JDBC Driver is also an SPI service

Mysql drivers are also loaded by the driver interface via SPI.

DriverManager calls the loadInitialDrivers method to load the driver service

// DriverManager.loadInitialDrivers()
private static void loadInitialDrivers() {
       AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {

            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
            Iterator<Driver> driversIterator = loadedDrivers.iterator();

            try{
                while(driversIterator.hasNext()) { driversIterator.next(); }}}}} / / com. Mysql. Cj). The JDBC Driver / / registered himself to the DriverManager static {try {Java. SQL. DriverManager. RegisterDriver (new  Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); }}Copy the code

Since the service is lazily loaded, iterators are iterated, and in the case of Mysql driver classes, DriverManager registers itself so that DriverManager manages all drivers.

Custom file name

In some cases, you can customize the ClassLoader to prevent normal access

For lombok, use ShadowClassLoader to load scl.lombok files.

Encrypt class files

Implement an encrypted class file and use a custom ClassLoader to load the demo.

  1. Encrypt class files

Use xOR encryption, because twice xOR is equal to the original value, is a relatively simple way, higher security can be through JNI or public and private key mode.

Public static byte[] decodecoassBytes (byte[] bytes) {byte[] decodedBytes = new byte[bytes.length];for (int i = 0; i < bytes.length; i++) {
      decodedBytes[i] = (byte) (bytes[i] ^ 0xFF);
    }
    return decodedBytes;
}
Copy the code
  1. Write encryption class class logic is relatively simple, construct when printing a sentence. The compiled class is encrypted using the method in the previous step and renamed to a.class_ file for differentiation.
public class MyClass {
  public MyClass(){
    System.out.println("My class"); }}Copy the code

The encrypted file cannot be parsed in the normal way. You can use the Javap command to verify this

D: \ workspace \ mygit \ JDK - learn \ jdk8 \ SRC/main/resources > javap - v lang. This. Encrypt. Myclass error: Read lang. This. Encrypt. An error occurred when the constant pool Myclass: unexpected tag at#1: 245
Copy the code
  1. Writing a custom ClassLoader first defines a boot class, which is loaded by the custom ClassLoader. The custom ClassLoader is then used when the bootstrap class creates the class. This process is the same as Tomcat custom classLoader.
Public class MyCustomClassLoader extends ClassLoader {private Collection<String> encryptClass = new HashSet<>(); Private Collection<String> skipClass = new HashSet<>(); public voidinit() {
    skipClass.add("lang.classloader.encrypt.EncryptApp");
    encryptClass.add("lang.classloader.encrypt.MyClass"); } @Override public Class<? > loadClass(String name) throws ClassNotFoundException {// Class loaded by the parent classif (name.startsWith("java.") &&! encryptClass.contains(name) && ! skipClass.contains(name)) {returnsuper.loadClass(name); } // Unencrypted classelse if (skipClass.contains(name)) {
      try {
        String classPath = name.replace('. '.'/') + ".class"; // Return the input stream for reading the specified resource URL resource = getClass().getClassLoader().getResource(classPath); InputStream is = resource ! = null ? resource.openStream() : null;if (is == null) {
          returnsuper.loadClass(name); } byte[] b = new byte[is.available()]; is.read(b); // Convert a byte array to an instance of the Class ClassreturndefineClass(name, b, 0, b.length); } catch (IOException e) { throw new ClassNotFoundException(name, e); }} // The encrypted classreturnfindClass(name); } @Override protected Class<? > findClass(String name) throws ClassNotFoundException {// Loading the content of the class file byte[] bytes = getClassFileBytesInDir(name); Byte [] decodedBytes = decodeClassBytes(bytes); byte[] decodedBytes = decodeClassBytes(bytes); // Initialize the class, implemented by the JVMreturndefineClass(name, decodedBytes, 0, bytes.length); Private static byte[] getClassFileBytesInDir(String className) throws ClassNotFoundException {try {private static byte[] getClassFileBytesInDir(String className) throws ClassNotFoundException {try {return FileUtils.readFileToByteArray(
          new File(className.replace("."."/ /") + ".class_")); } catch (IOException e) { throw new ClassNotFoundException(className, e); }}}Copy the code
  1. When the test program tests, it first creates a custom class loader, and then uses the custom class loader to load the startup class, which uses the custom class loader to load MyClass.

Instructions for calling the EncryptApp methods through reflection are important, and you can try direct type conversions to see what exceptions are thrown.

public class EncryptApp {
  public void printClassLoader() {
    System.out.println("EncryptApp:" + this.getClass().getClassLoader());
    System.out.println("MyClass.class.getClassLoader() = "+ MyClass.class.getClassLoader()); new MyClass(); } } public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { MyCustomClassLoader myCustomClassLoader = new MyCustomClassLoader(); myCustomClassLoader.init(); Class<? > startupClass = myCustomClassLoader.loadClass("lang.classloader.encrypt.EncryptApp"); Important: / / // the classloader of the current thread is not the same as that used to load the EncryptApp, so the type conversion cannot be done. Must use the object object encryptApp = startupClass. GetConstructor (). The newInstance (); String methodName ="printClassLoader";
    Method method = encryptApp.getClass().getMethod(methodName);
    method.invoke(encryptApp);
  }
Copy the code

The results are as follows:

/ / EncryptApp is loaded by MyCustomClassLoader EncryptApp: lang. This. The encrypt. 1 a6c5a9e / / EncryptApp MyCustomClassLoader @ Start the class loading MyClass is also used MyCustomClassLoader MyClass. Class. GetClassLoader () = lang.classloader.encrypt.MyCustomClassLoader@1a6c5a9e My classCopy the code

conclusion

ClassLoader is an important tool, but you rarely need to customize a ClassLoader. Loading bytecode via a custom ClassLoader is still exciting.

Understanding a ClassLoader from its life cycle gives you a better idea of what it can do. Many times it needs to be combined with bytecode technology to maximize its power. Many frameworks do the same, such as APM.

The resources

  • Understanding the Java Virtual Machine in Depth: Advanced JVM Features and Best Practices (version 3)
  • In-depth understanding of JVM bytecode