Flink的classLoader加载机制(推测)-- 记一次程序问题中的探索

    项目中需要用flink去加载c++的so文件。flink任务中如果有加载so的逻辑,当任务挂掉之后,再次重启的时候会报“Native Library xxx is being loaded in another classloader”的错误(这个过程中Flink集群的进程没退出)。

    通过分析System.loadLibrary(String libname)方法,发现加载后的so文件会记录在内存里,会记录一些信息,包括so文件名、加载这个so的类所用的classLoader(每个类都是被一个classLoader加载的,如果同一个类被不同的classLoader加载,这个类在内存里是不一样的,虽然他们的源头相同)。如果在加载某个so文件的时候,发现该so文件被加载过了,且加载这个so的classLoader和当前classLoader不相同,那么就会报上面的错误。

     由此推测,这个应该是flink每个任务都有自己的classLoader(这个在看flink任务分发的代码时有看到过,jobmanager把解析后的job分发给taskmanager的时候会把jobmanager中使用的classloader也发出去),so是任务自定义的classLoader加载的,当任务重启的时候,又生成了一个新的classLoader,它去加载so,结果发现已经加载过了。

    这个问题推测可以用一下两种方法解决:

     (1)每次修改so文件的文件名,这样每次加载的so文件不是一样的,也就不冲突了。(已实验过,可行)

     (2)用更高层的classLoader进行so的加载,比如bootStrapClassLoader,这种是进程共享的,这就避免了每次都由任务自己的ClassLoader去加载so文件。

 

第一个方案使用过,是可行的。第二个方案在理论上进行了实验,并没有真正的使用在项目中。

     

使用ExtClassLoader加载so文件

研究了一下java的ClassLoader机制,感觉可以用ExtClassLoader加载对应的类,让这个类去load   so文件

那么,怎么能让类被ExtClassLoader加载呢?做了一个小实验,说明这个逻辑。

A.java

public class A {
    public static String s = "test";
    public static void main(String[] args) {
        System.out.println(A.class.getClassLoader());
    }
}
$ javac A.java
$ jar -cvf A.jar A.class
# 打好的jar包中有A.class 和META-INF/MANIFEST.MF。但是META-INF/MANIFEST.MF文件中没有指定Main,所以不可以直接执行,要想直接执行,采用下面的操作
$ jar -xf A.jar
# 给MANIFEST.MF 文件加入“Main-Class: A”,然后重新打包
$ jar -cvfm A.jar META-INF/MANIFEST.MF A.class
$ java -jar A.jar
sun.misc.Launcher$AppClassLoader@7852e922


# 然后,把A.jar放到${JAVA_HOME}/jre/lib/ext目录下
$ java -jar A.jar
sun.misc.Launcher$ExtClassLoader@6d06d69c

上面可以看出,把jar包放到ext目录下之后,A类被扩展类加载器加载,其他所有的类都可以调用这个类使用。

那么,可以把loadLibrary的逻辑放在ext目录下,这样不管是哪个程序调用这个类,去加载so的时候都不会重复加载,而且只要jvm还存在,这个加载关系就一直能存在。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章