在项目Q中,使用Mesos进行资源隔离和任务调度。调度的任务类型包括一些Hadoop相关任务,在某次升级Hadoop集群之后,这些任务出错,跟踪日志发现是Mesos和Hadoop依赖的Protobuf版本出现了冲突,升级或降级Protobuf都不能解决问题。同时,在另外一个负责数据传输的项目D中,随着数据传输场景的多样化,项目D开始要和不同类型的输入输入打交道,包括不同版本的Hdfs、HBase和Hive集群等,同样出现了各种jar包冲突问题。按照经验,此类问题适合使用自定义类加载器来解决,但一路下来,磕磕碰碰踩了不少坑,记录下来。
原理学习
所谓类加载,就是虚拟机通过类名称获取类的二进制字节流,然后在方法区中生成代表这个类的Class对象的过程,而开发人员自定义类加载器能控制的就是如何获取字节流方式进行加载这一步。当然一个类能够被使用,还必须经过链接(验证+准备+解析)和初始化(clinit静态变量初始化和运行静态语句块)。
类加载过程
java提供了三种类加载器:
1. Bootstrap启动类加载器,负责加载jdk_home/lib目录下的核心api类。
2. Extension扩展类加载器,负责加载jdk_home/lib/ext目录下的jar包或-Djava.ext.dirs指定目录下jar包。
3. Application应用类加载器,负责加载用户类路径ClassPath下jar包。
其中,用户自定义类加载器的父类加载起是3,3的父是2,2的父是1。。。类加载过程使用双亲委派模型,即先交给父类加载器去加载,只有父类加载器无法加载再交给子类去完成,最后才会由用户自定义类加载器来加载。
源码分析
首先注意的是父类加载器是父“类加载器”而不是“父类”加载器,即双亲委派不是通过继承关系实现的,扩展类加载器和应用类加载器都是URLClassLoader的子类。
双亲关系通过parent成员变量在构造函数中初始化:
public abstract class ClassLoader {
// The parent class loader for delegation
// Note: VM hardcoded the offset of this field, thus all new fields
// must be added *after* it.
private final ClassLoader parent;
……
}
委派加载流程在loadClass方法中实现:
protected Class<?> loadClass(String name,