"In-depth understanding of the Java virtual machine" reading notes (eight)-class loading and execution subsystem case (Tomcat class loading, OSGI, dynamic proxy)

One, Tomcat class loader architecture

As a web server, the following problems need to be solved:

  • Java class libraries used by web applications deployed on the same server can be isolated from each other.
  • The Java class libraries used by two web applications deployed on the same server can be shared with each other.
  • The server needs to ensure that its own security is not affected by the deployed web application as much as possible.
  • JSP needs to support hot updates

Due to the above problems, a single classpath cannot meet the demand, so the web server provides several classpath paths for users to store third-party class libraries. In the Tomcat directory structure, there are 3 sets of directories ("/common/*", "/server/*" and "/shared/*"), plus the web application's own directory "/WEB-INF/*" .

  • /common directory: The class library can be used by Tomcat and all web applications.

  • /server directory: The class library can be used by Tomcat and is invisible to all web applications.

  • /shared directory: The class library can be used by all web applications, but it is not visible to Tomcat itself.

  • /webapp/WEB-INF directory: The class library can only be used by this micro web application, and is not visible to Tomcat and other web applications.

Tomcat has customized a number of class loaders, which are implemented in accordance with the classic parental delegation model.

Tomcat 5.x class loader structure (net diagram)

Among them, the top-level startup class loader, extended class loader, and application class loader are all class loader provided by the JDK by default. Another CommonClassLoader is responsible for loading the Java class library in "/common/*", CatalinaClassLoader is responsible for loading the Java class library in "/server/*", SharedClassLoader is responsible for loading the Java class library in "/shared/*", and WebAppClassLoader is responsible for Load the respective Java class libraries in "/WebApp/WEB-INF/*", and finally each jsp file corresponds to a class loader.

Note: After the 6.x version of Tomcat, common, server, and shared are merged into the lib directory by default. The class library in this directory is equivalent to the role played by the class library in the previous common directory. If the default settings cannot meet the requirements, the user can re-enable the 5.x loader structure by modifying the configuration file to specify server.loader and share.loader

As can be seen from the above figure, the classes that CommonClassLoader can load can be loaded by the CatalinaClassLoader and SharedClassLoader class loaders, while the classes that CatalinaClassLoader and SharedClassloader can load themselves are isolated from each other. WebClassLoader can use the classes loaded by SharedClassLoader (including upper class loader), but each instance of WebAppClassLoader is isolated from each other. The loading range of JasperLoader is only the class compiled by this jsp file. When the server detects that the jsp file has been modified, it will replace the current JasperLoader instance, and create a new jsp class to load to achieve jsp Hot update function (about JSP can refer to how JSP is compiled into servlet and provide services ).

Regarding Violation of Parental Delegation

As mentioned above, for web containers, there is an important problem that needs to be solved: for different webapps, the jar packages in their respective directories may need to be isolated from each other. A simple example is that different applications deployed in the same container may depend on different versions of the same jar package, so they must be isolated from each other. To achieve isolation is also very simple. Each application has a class loader corresponding to its own directory, and these jar packages are stored in their own application directory and loaded by their respective class loader to achieve mutual isolation. But from this description, it doesn't actually violate the parental delegation model, it mainly depends on how to achieve it.

To achieve this goal, there are two solutions in terms of realization:

  • If a common parent class loader and an application-specific class loader can load these jar packages, then the control application’s respective class loader will not be handed over to the parent class loader to load first according to the recommendations of the parent delegation, but directly Loaded by myself

  • Following the parental delegation model, the application-specific class loader still entrusts the parent class loader to load first, but it is necessary to ensure that all their common parent class loader cannot load these jar packages, so that according to the parental delegation backtracking, it will still be loaded by itself

二、OSGI

OSGI (Open Service Gateway Initiative) is a dynamic modular specification based on the Java language formulated by the OSGI Alliance. Each module in OSGI (called Bundle) can declare the Java Package it depends on (described by Import-Package), or it can declare that it allows to export the released Java-Package (described by Export-Package), and each module starts from In appearance, it appears to be a dependency between peers. One of the great advantages of OSGI is that OSGI-based programs are likely to be able to achieve module-level hot-plugging functions, thanks to its flexible class loader architecture.

OSGI's Bundle class loader only had rules before, and there was no fixed delegation relationship. For example, a Bundle declares a package that it depends on. If other Bundles declare to release this package, then all class loading actions for this package will be delegated to the Bundle class loader that released it to complete. When a specific Package is not involved, each Bundle loader has a level relationship. Only when a specific Package and Class are used, will the delegation and dependencies between the Bundles be constructed according to the package import and export definitions.

Suppose there are three modules, BundleA, BundleB, and BundleC, and their defined dependencies are as follows:

  • BundleA: Release packageA, dependent on java.*

  • BundleB: depends on packageA, packageC and java.*

  • BundleC: release packageC, depend on packageA

Then the class loading relationship between these three Bundles is shown in the following figure:

OSGI's class loader architecture

 

It can be seen that the relationship between loaders in OSGI is no longer the tree structure of the parent delegation model, but has developed into a more complex network structure that can only be determined at runtime . In the early days, this kind of non-tree structured class loader architecture was prone to deadlock due to interdependence. Take OSGI as an example. If BundleA depends on BundleB's packageB, BundleB depends on BundleA's packageA: When BundleA loads PackageB, First lock the instance object of the current class loader (ClassLoader.loadClass() is a synchronized method), and then delegate the request to the class loader of BundleB, but if BundleB also just wants to load the class of packageA, you need to first Lock your own class loader and then request BundleA's loader to process, so that both loaders are waiting for each other to process their own requests, but each holds its own lock, causing a deadlock state.

In JDK1.7, a special upgrade was made for the non-tree inheritance class loader architecture. Refer to: Concurrency Control Lock for Class Loading (ClassLoadingLock)

3. Bytecode generation technology and dynamic proxy

Compared with dynamic proxy, static proxy implementation is relatively simple, just let the proxy class replace the proxy class to execute the method, of course, the premise is that the same interface is implemented or the same parent class is inherited. Here is a simple example:

public class StaticProxyTest {
    interface Runnable {
        void run();
    }

    class Dog implements Runnable {
        @Override
        public void run() {
            System.out.println("dog run.");
        }
    }

    static class StaticProxy implements Runnable {
        Runnable originObj;

        public Runnable bind(Runnable obj) {
            this.originObj = obj;
            return this;
        }

        @Override
        public void run() {
            System.out.println("before run.");
            originObj.run();
        }
    }

    public static void main(String[] args) {
        new StaticProxy().bind(new StaticProxyTest().new Dog()).run();
    }
}

It can be seen that the static proxy implementation is very simple, and there is no intrusion to the proxy object, but it is limited to this simple scenario. Static proxy requires the original class and interface to be known at compile time and is not dynamic, and if the proxy class implements multiple interfaces, it is not so easy to maintain. The dynamic proxy implementation in Java can determine the proxy behavior of the proxy class when the original class and interface are not yet known. The proxy class is separated from the original class directly, and can be flexibly reused in different application scenarios. The corresponding implementation is also relatively simple, mainly relying on two classes: Proxy and InvocationHandler, the following is a simple example:

public class DynamicProxyTest {
    interface Speakable {
        void speak();
    }

    interface Runnable {
        void run();
    }

    class Dog implements Runnable, Speakable {

        @Override
        public void run() {
            System.out.println("dog run.");
        }

        @Override
        public void speak() {
            System.out.println("dog speak.");
        }
    }

    static class DynamicProxy implements InvocationHandler {
        //原始对象
        Object originObj;

        public Object bind(Object obj) {
            this.originObj = obj;
            //生成代理对象
            return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("before method:" + method.getName());
            return method.invoke(originObj);
        }
    }

    public static void main(String[] args) {
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        Object proxy = (new DynamicProxy().bind(new DynamicProxyTest().new Dog()));
        ((Runnable) proxy).run();
        ((Speakable) proxy).speak();
    }
}

The logic of the above dynamic proxy is very clear. The only thing that is not clear is the Proxy.newProxyInstance method. How does this method return the proxy object? We all know that the principle of dynamic proxy is to create a new proxy class (bytecode) based on the proxy interface and InvocationHandler instance we provide. Since source code analysis is not the focus of this series of articles, only part of the core logic is posted here:

The Proxy.newProxyInstance method will eventually generate proxy class bytecode through a static internal class ProxyClassFactory.apply(classLoader, interfaces) method of java.lang.reflect.Proxy class and complete bytecode analysis (part of the code):

private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>
    {
        // prefix for all proxy class names
        private static final String proxyClassNamePrefix = "$Proxy";

        // next number to use for generation of unique proxy class names
        private static final AtomicLong nextUniqueNumber = new AtomicLong();

        @Override
        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
            .......
           /*
             * Generate the specified proxy class.
             * 这里生成代理类字节码内容
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                /*
                 * A ClassFormatError here means that (barring bugs in the
                 * proxy class generation code) there was some other
                 * invalid aspect of the arguments supplied to the proxy
                 * class creation (such as virtual machine limitations
                 * exceeded).
                 */
                throw new IllegalArgumentException(e.toString());
            }
            ......
        }
    }

In the meantime, ProxyGenerator.generateProxyClass will be called to generate a proxy class. Add a statement in the main method:

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true") is to output the automatically generated proxy class. After running, it is found that there is a $Proxy0 class in the directory:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.demo.aop;

import com.demo.aop.DynamicProxyTest.Runnable;
import com.demo.aop.DynamicProxyTest.Speakable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

final class $Proxy0 extends Proxy implements Runnable, Speakable {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m4;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void run() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void speak() throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.demo.aop.DynamicProxyTest$Runnable").getMethod("run");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("com.demo.aop.DynamicProxyTest$Speakable").getMethod("speak");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

As you can see, the automatically generated proxy class inherits java.lang.reflect.Proxy by default, implements the interface implemented by the proxy class, and calls the invoke method to finally call the invoke method we implemented. Since Java is single inheritance, the dynamic proxy provided by Java requires that the proxy class must implement the interface, so in the spring aop, InvocationHandler and cglib are provided in two proxy modes.

Guess you like

Origin blog.csdn.net/huangzhilin2015/article/details/114554579