vlambda博客
学习文章列表

tomcat下部署多应用加载公共JNI动态库的解决方案

有一个项目需求是这样的:一个tomcat实例,监听一个端口来部署多个应用或者监听多个端口部署多个应用。

这些项目都有一个共性,都使用了JNI来完成某些具体的功能,需要加载动态库so,具体的代码如下:

public class JNI {
  static {
        System.loadLibrary("jni");
    }
  public native byte[] digest(String data);
}

上面的代码大致意思就是在服务启动的时候JVM会加载libjni.so,然后应用就可以调用digest方法来完成相应的操作并得到结果。

每个应用打包后,分别部署在一个tomcat实例下,webapps、webapps1、webapps2端口分别为8080、8081、8082,tomcat版本为8.5.68,部署情况大致如下:

启动成功之后,进行功能测试:

  • 访问ip:8080/demo,功能实现正常

  • 访问ip:8081/demo和ip:8082/demo,均提示Native Library jni.so already loaded in another classloader

问题分析

根据错误提示信息,应该是JVM不允许同一个JNI本地库被不同的classloader加载。

根据tomcat的类加载机制,每个应用都有一个WebApp Classloader来加载,所以libjni.so在应用使用时,第一个WebApp Classloader加载成功后,这个库就不能再被其他WebApp Classloader去加载了。

解决思路与方案

既然问题是由于类加载机制引起的,那么我们可以根据tomcat的类加载机制方向去解决。也就是只让一个类加载器去加载jni,并且可以让每个应用共享,问题不就解决了?问题解决就需要先分析一个tomcat的类加载机制。

1.Tomcat 的类加载机制

Tomcat 的类加载机制相对于 Jvm 的类加载机制做了⼀些改变,没有严格的遵从双亲委派机制。

Tomcat 的类加载在 Jvm 的类加载的基础上增加了Common Classloader、Catalina Classloader 、Shared ClassLoader、WebApp Classloader

  • Common Classloader通⽤类加载器加载Tomcat使⽤以及应⽤通⽤的⼀些类,位于CATALINA_HOME/lib下,

    ⽐如servlet-api.jar

  • Catalina ClassLoader ⽤于加载服务器内部可⻅类,这些类应⽤程序不能访问

  • SharedClassLoader:各个WebAPP共享的类加载器,加载路径下的类库可被所有的Web应用程序共同使用,但是对Tomcat不可见。

  • WebappClassLoader:各个Webapp私有的类加载器,加载路径下的类库仅仅可被此Web应用程序使用,对Tomcat和其他的Web应用程序都不可见。

2.解决方案

了解了Tomcat 的类加载机制之后,可以看到Common Classloader通⽤类加载器是Tomcat和WebAPP可以共用的,各个应用所共用的JNI本地库交给Common Classloader来加载,这样就满足了只有一个Classloader来加载JNI本地库,并且每个WebAPP都可以使用。

3.验证

有了解决思路,便来验证一下:

1.首先将使用到的native方法抽离,聚合到一个类里,打成jar后提供给每个应用依赖,如果已经有jar就不需要此步骤了,本文中就是将所有native方法放在了JNI.java里了,然后打包成JNI.jar提供给其他应用使用。

2.依赖JNI.jar,我是使用maven进行依赖管理的,依赖方式如下:

<dependency>
        <groupId>com.java.demo</groupId>
        <artifactId>jni</artifactId>
        <version>1.0</version>
        <scope>provided</scope>
</dependency>

添加<scope>provided</scope>是为了jni.jar不被打包进应用的WEB-INF/lib里

3.将JNI.jar放入tomcat/lib目录下

4.分别部署多个应用,然后启动tomcat

5.分别访问ip:8080/demo、ip:8081/demo、ip:8082/demo发现调用JNI部分的功能均已正常

由此问题得以解决。

总结

tomcat打破了JVM的类加载机制(双亲委派),由WebApp Classloader先加载,每个应用都对应一个WebApp Classloader加载器。

多个应用下,如果引入了相同jar,那么就可以放在tomcat/lib目录下由Common Classloader统一去加载。