容器如何做隔离?tomcat类加载分析

最近在使用公司的RPC框架,突然想到一个问题,公司的RPC框架本质上是个容器,可以部署多个服务,框架本身要用到netty实现网络传输,而部署的服务也要用netty,比如某些服务使用了redisson,就需要用netty。那么问题来了,如何实现隔离呢?tomcat也是大家日常使用最多的Servlet容器,tomcat下可以部署很多应用(webapp),有不同的上下文,每个上下文下有很多Servlet,每个webapp有自己的lib和class,tomcat的资源隔离如何实现的,下面就结合官方文档和源码,分析一下。

首先了解一些java类加载的知识点:
  • 每个class对象都持有加载它的classloader,两个class对象使用equals来比较,不仅比较两个class的字节码一样,还比较加载它们的classloader,不同的classloader加载同一个jar包里的类,是不一样的,强转会抛异常
  • 类加载器所创建对象的方法和构造方法可以引用其他类。 为了确定引用的类,Java 虚拟机将调用最初创建该类的类加载器的 loadClass 方法。 这点很重要,这就意味着当执行Servlet的方法时,加载Servlet的类加载器会加载方法中引用到的类!

java.lang.ClassLoader The methods and constructors of objects created by a class loader may reference other classes. To determine the class(es) referred to, the Java virtual machine invokes the loadClass method of the class loader that originally created the class.

提个小问题:

在Servlet的方法中启动新的线程,类加载是怎样的?具体到spring/springmvc,我们知道有一个DispatcherServlet来处理几乎所有的请求,那么在Controller乃至Service方法中启动新的线程/线程池,这些线程在执行方法时,类加载如何处理?

tomcat类加载文档 地址 ,建议仔细阅读。

容器如何做隔离?tomcat类加载分析_第1张图片

Tomcat的Bootstrap启动类加载器和JVM的Bootstrap启动类加载器其实不一样,JVM默认有三个类加载器,如下:

引导类加载器(bootstrap class loader): 它用来加载 Java 的核心库(jre/lib/rt.jar),是用原生C++代码来实现的,并不继承自java.lang.ClassLoader。加载扩展类和应用程序类加载器,并指定他们的父类加载器,在java中获取不到。
扩展类加载器(extensions class loader): 它用来加载 Java 的扩展库(jre/ext/*.jar)。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
系统类加载器(system class loader): 它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。

Tomcat的Bootstrap加载器 就相当于jvm的Bootstrap加载器加上扩展类加载器。加载的就是jvm运行所需class。
Tomcat的System加载器 就是JVM的系统加载器,也就是我们常见的AppClassLoader,它主要负责tomcat的启动,入口函数org.apache.catalina.startup.Bootstrap#main()
Tomcat的Common加载器 加载的就是tomcat的lib下的jar
Webapp加载器 就是webapps目录下的每一个应用对应一个,负责WEB-INF/classes和WEB-INF/lib下的类加载。
注意一点,有一个配置可以决定Webapp加载器在加载类时的搜索顺序。默认值是false。 容器如何做隔离?tomcat类加载分析_第2张图片
如果配置为true,则搜索顺序是这样,这一点在org.apache.catalina.loader.WebappClassLoaderBase#loadClass(String name)可以很清楚的看到:
容器如何做隔离?tomcat类加载分析_第3张图片

Tomcate请求处理过程

容器如何做隔离?tomcat类加载分析_第4张图片

根据请求路径可以找到Context,每个Context对应一个WebappClassLoader,根据请求可以定位到Servlet,实例化Servlet的时候,先用WebappClassLoader加载Servlet,然后用反射new一个Servlet实例。
当执行Servlet的方法时,就会使用加载此Servlet的加载器去加载引用类,如果没有且delegate属性是false,先在jre下找,然后在WEB-INF下找,然后是Tomcat的System加载器,然后是Common加载器。

上面问题的思考方向

我们知道java有SPI机制,比如比较老的JDBC代码,需要手动加载驱动类,Class.forName("com.mysql.jdbc.Driver"),后来就不用这么写了,因为Java会扫描并使用线程上下文加载器来加载驱动,我想tomcat处理请求的线程设置线程上下文加载器,肯定与这个有关吧。

你可能感兴趣的:(tomcat,java,servlet,jvm,开发语言)