美文网首页
老生常谈--Java 中 classloader

老生常谈--Java 中 classloader

作者: jibiyr | 来源:发表于2021-01-20 14:09 被阅读0次

    Java 中classloader 无时无刻都在使用,但是其中一些细节一直没有思考清楚。

    jdk默认classloader

    • Bootstrap ClassLoader(加载JDK的/lib目录下的类)
      -Xbootclasspath:<path> 替换bootclasspath为<path>
      -Xbootclasspath/a:<path> 将<path> 追加到bootclasspath之后
      -Xbootclasspath/p:<path> 将<path> 附加到bootclasspath之前

    • Extension ClassLoader(加载JDK的/lib/ext目录下的类)

    • Application ClassLoader(程序自己classpath下的类)
      也就是-cp --classpath指定的路径的类

    自定义classloader

    自定义classloader 的话主要是实现 findClass 这个模板方法。

    类似

              public Class findClass(String name) {
                   byte[] b = loadClassData(name);
                   return defineClass(name, b, 0, b.length);
               }
    

    其中一般主要区别是这个byte[] b的获取,可以是网络流中读取,亦或者是磁盘文件、hdfs等等。

    打破双亲委派

    先说为什么要有双亲委派,因为安全性,不想默认的bootstrap path下的类被用户自定义的同限定名类覆盖,比如java.lang.String这个常用类。
    确保获取到的String.class总是bootstrapPath下面的那个String

    打破双亲委派很简单,不是去实现findClass这个模板模式的方法,转而去实现loadClass这个主调入口方法。

    为什么要打破双亲委派

    这个一般出现在开发框架,然后去加载用户上传的jar包中,比如spark flink此类都有自己的类加载器,如果不用自定义类加载器,以spark为例,他的一个executor启动时候用默认的classloader,你要这样去启动

    java -cp $spark.jar:...:$user.jar... Main.class
    

    一般来说,spark中和用户程序可能会存在相同的类库的不同版本,比如他们包名相同,此时加载时候,写在命令行前面的就会覆盖后面的。

    spark中自定义了类加载器,就能解决这个问题,其中就打破了parent优先。
    具体如何设置,就是通过

    Thread.currentThread().setContextClassLoader() 
    

    这样task线程执行用户代码获取到的class,就与框架运行所需的同名class来自不同的类加载器。就算有同包名的类也不会冲突。

    instanceOf

    instanceOf 用来判断两个对象的类的继承关系,如果是不同类加载器加载的类,也是会返回false。
    类似的情况还包括我们在类型强转出现ClassCastException,用==比较class实例的时候。
    一般这时候就要排查下是否是类加载器的问题,可以打印

    Class.getClassLoader() 
    

    来确认。

    框架中的坑

    某些解析框架使用时候有这样一组方法,比如SnakeYaml

    Yaml.parse(String input,String clazz,ClassLoader classloader)
    

    如果用户自定义classloader,SnakeYaml内部就无法根据类限定名获取Class实例,因为他只能获取当前线程的classLoader。

    相关文章

      网友评论

          本文标题:老生常谈--Java 中 classloader

          本文链接:https://www.haomeiwen.com/subject/acaqzktx.html