本文的Flink源码版本为: 1.15-SNAPSHOT,读者可自行从Github clone.

要讲解 Flink 的类加载机制,首先你得对 JDK 的类加载机制有所了解。

推荐阅读我之前写的1篇博客: 基于源码深入了解Java的类加载机制(JDK8和JDK11双版本)

接着看一下 FLink 的类加载器继承结构:

FlinkUserCodeClassLoader 继承自 URLClassLoader 类,其 loadClass() 方法实现如下:

@Override
public final Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {try {synchronized (getClassLoadingLock(name)) {return loadClassWithoutExceptionHandling(name, resolve);}} catch (Throwable classLoadingException) {classLoadingExceptionHandler.accept(classLoadingException);throw classLoadingException;}
}protected Class<?> loadClassWithoutExceptionHandling(String name, boolean resolve)throws ClassNotFoundException {// 本质上还是调用的 ClassLoader 的 loadClass() 方法// 即 FlinkUserCodeClassLoader 仍然遵循双亲委派模型return super.loadClass(name, resolve);
}

FlinkUserCodeClassLoader 有2个子类,分别为 ParentFirstClassLoader 和 ChildFirstClassLoader。

ParentFirstClassLoader

public static class ParentFirstClassLoader extends FlinkUserCodeClassLoader {ParentFirstClassLoader(URL[] urls, ClassLoader parent, Consumer<Throwable> classLoadingExceptionHandler) {// 直接复用父类 FlinkUserCodeClassLoader 的相关方法逻辑super(urls, parent, classLoadingExceptionHandler);}static {ClassLoader.registerAsParallelCapable();}
}

可以看到,Parent-First 类加载策略照搬双亲委派模型,也就是说,用户代码的类加载器是User ClassLoader,Flink 框架本身的类加载器是 Application ClassLoader,用户代码中的类先由 Flink 框架的类加载器加载,再由用户代码的类加载器加载。

双亲委派模型的好处是随着加载器的层次关系保证了被加载类的层次关系,从而保证了 Java 运行环境的安全性。但是在 Flink App 这种依赖纷繁复杂的环境中,双亲委派模型可能并不适用。例如,程序中引入的 Flink-Kafka Connector 总是依赖于固定的 Kafka 版本,用户代码中为了兼容实际使用的 Kafka 版本,会引入一个更低或更高的依赖。而同一个组件不同版本的类定义可能会不同(即使类的全限定名是相同的),如果仍然用双亲委派模型,就会因为 Flink 框架指定版本的类先加载,而出现莫名其妙的兼容性问题,如 NoSuchMethodError、IllegalAccessError等。

鉴于此,Flink 实现了 ChildFirstClassLoader 类加载器并作为默认策略,它打破了双亲委派模型,使得用户代码的类先加载,官方文档中将这个操作称为 “Inverted Class Loading”。

ChildFirstClassLoader

ChildFirstClassLoader 是通过覆写 FlinkUserCodeClassLoader 的 loadClassWithoutExceptionHandling() 方法来实现 Child-First 逻辑的。

@Override
protected Class<?> loadClassWithoutExceptionHandling(String name, boolean resolve)throws ClassNotFoundException {// 首先调用 findLoadedClass 方法检查全限定名 name 对应的类是否已加载过Class<?> c = findLoadedClass(name);if (c == null) {// 检查要加载的类是否以 alwaysParentFirstPatterns 集合中的前缀开头。如果是,则调用父类的 loadClassWithoutExceptionHandling 方法,以 Parent-First 的方式加载它for (String alwaysParentFirstPattern : alwaysParentFirstPatterns) {if (name.startsWith(alwaysParentFirstPattern)) {return super.loadClassWithoutExceptionHandling(name, resolve);}}try {// 若加载的类不以 alwaysParentFirstPatterns 集合中的前缀开头,则调用父类 URLClassLoader 的 findClass 方法进行类加载c = findClass(name);} catch (ClassNotFoundException e) {// 若调用 findClass() 方法失败// 最终再调用父类的 loadClassWithoutExceptionHandling 方法,以 Parent-First 的方式加载它c = super.loadClassWithoutExceptionHandling(name, resolve);}} else if (resolve) {resolveClass(c);}return c;
}

可见,用户如果仍然希望某些类"遵循祖制",采用双亲委派模型来加载,则需要借助 alwaysParentFirstPatterns 集合来实现。

而该集合主要由以下2个参数来指定:

  • classloader.parent-first-patterns.default,默认值如下:
@Documentation.Section(Documentation.Sections.EXPERT_CLASS_LOADING)
public static final ConfigOption<List<String>> ALWAYS_PARENT_FIRST_LOADER_PATTERNS =ConfigOptions.key("classloader.parent-first-patterns.default").stringType().asList().defaultValues(ArrayUtils.concat(new String[] {"java.","scala.","org.apache.flink.","com.esotericsoftware.kryo","org.apache.hadoop.","javax.annotation.","org.xml","javax.xml","org.apache.xerces","org.w3c","org.rocksdb."},PARENT_FIRST_LOGGING_PATTERNS)).withDeprecatedKeys("classloader.parent-first-patterns").withDescription("A (semicolon-separated) list of patterns that specifies which classes should always be"+ " resolved through the parent ClassLoader first. A pattern is a simple prefix that is checked against"+ " the fully qualified class name. This setting should generally not be modified. To add another pattern we"+ " recommend to use \"classloader.parent-first-patterns.additional\" instead.");@Internal
public static final String[] PARENT_FIRST_LOGGING_PATTERNS =new String[] {"org.slf4j","org.apache.log4j","org.apache.logging","org.apache.commons.logging","ch.qos.logback"};

该值一般不推荐修改。

  • classloader.parent-first-patterns.additional

用户如果仍然希望某些类"遵循祖制",采用双亲委派模型来加载,则可以通过该参数额外指定,并以分号分隔。

@Documentation.Section(Documentation.Sections.EXPERT_CLASS_LOADING)
public static final ConfigOption<List<String>> ALWAYS_PARENT_FIRST_LOADER_PATTERNS_ADDITIONAL =ConfigOptions.key("classloader.parent-first-patterns.additional").stringType().asList().defaultValues().withDescription("A (semicolon-separated) list of patterns that specifies which classes should always be"+ " resolved through the parent ClassLoader first. A pattern is a simple prefix that is checked against"+ " the fully qualified class name. These patterns are appended to \""+ ALWAYS_PARENT_FIRST_LOADER_PATTERNS.key()+ "\".");

若有需要,在 conf/flink-conf.yaml 中配置 classloader.parent-first-patterns.additional即可。

Flink 会将上述2种配置合并在一起,作为 alwaysParentFirstPatterns 集合:

public static String[] getParentFirstLoaderPatterns(Configuration config) {List<String> base = config.get(ALWAYS_PARENT_FIRST_LOADER_PATTERNS);List<String> append = config.get(ALWAYS_PARENT_FIRST_LOADER_PATTERNS_ADDITIONAL);return mergeListsToArray(base, append);
}

通过IDEA,很容易找到整个方法的调用路径:

ClientUtils.buildUserCodeClassLoader()–>FlinkUserCodeClassLoaders.create()–>FlinkUserCodeClassLoaders.childFirst()–>CHildFirstClassLoader()

// ClientUtils.buildUserCodeClassLoader() 方法
public static URLClassLoader buildUserCodeClassLoader(List<URL> jars, List<URL> classpaths, ClassLoader parent, Configuration configuration) {URL[] urls = new URL[jars.size() + classpaths.size()];for (int i = 0; i < jars.size(); i++) {urls[i] = jars.get(i);}for (int i = 0; i < classpaths.size(); i++) {urls[i + jars.size()] = classpaths.get(i);}// 此处用于获取 alwaysParentFirstPatterns 集合final String[] alwaysParentFirstLoaderPatterns =CoreOptions.getParentFirstLoaderPatterns(configuration);// 此处指定了默认的类加载机制为 child-firstfinal String classLoaderResolveOrder =configuration.getString(CoreOptions.CLASSLOADER_RESOLVE_ORDER);FlinkUserCodeClassLoaders.ResolveOrder resolveOrder =FlinkUserCodeClassLoaders.ResolveOrder.fromString(classLoaderResolveOrder);final boolean checkClassloaderLeak =configuration.getBoolean(CoreOptions.CHECK_LEAKED_CLASSLOADER);return FlinkUserCodeClassLoaders.create(resolveOrder,urls,parent,alwaysParentFirstLoaderPatterns,NOOP_EXCEPTION_HANDLER,checkClassloaderLeak);
}@Documentation.Section(Documentation.Sections.EXPERT_CLASS_LOADING)
public static final ConfigOption<String> CLASSLOADER_RESOLVE_ORDER =ConfigOptions.key("classloader.resolve-order").stringType().defaultValue("child-first").withDescription("Defines the class resolution strategy when loading classes from user code, meaning whether to"+ " first check the user code jar (\"child-first\") or the application classpath (\"parent-first\")."+ " The default settings indicate to load classes first from the user code jar, which means that user code"+ " jars can include and load different dependencies than Flink uses (transitively).");

本文到此结束,感谢阅读!

Flink进阶系列--类加载机制相关推荐

  1. Flink进阶系列--FLIP-27新的Source架构

    Source 旧架构 在 Flink 1.12之前,开发一个新的 source connector 是通过实现 SourceFunction 接口来完成的. @Public public interf ...

  2. JVM基础系列第7讲:JVM 类加载机制

    当 Java 虚拟机将 Java 源码编译为字节码之后,虚拟机便可以将字节码读取进内存,从而进行解析.运行等整个过程,这个过程我们叫:Java 虚拟机的类加载机制.JVM 虚拟机执行 class 字节 ...

  3. 面试必会系列 - 1.4 类加载机制

    本文已收录至 github,完整图文:https://github.com/HanquanHq/MD-Notes 类加载机制 类加载机制,类加载的过程? class loading 加载:JVM 把描 ...

  4. flink 三种时间机制_Flink时间系列:Event Time下如何处理迟到数据

    Event Time语义下我们使用Watermark来判断数据是否迟到.一个迟到元素是指元素到达窗口算子时,该元素本该被分配到某个窗口,但由于延迟,窗口已经触发计算.目前Flink有三种处理迟到数据的 ...

  5. 「JVM 系列」- JVM的类加载机制

    前言 在类的生命周期中,第一个阶段是就是类的加载阶段,在这个阶段,非数组类的二进制字节流被加载进内存中,并生成一个java.lang.Class对象. 本文主要论述发生在这一阶段的故事. 一. 类加载 ...

  6. java url路径包含中文_谈谈 Java 类加载机制

    概述 类加载器主要分为两类,一类是 JDK 默认提供的,一类是用户自定义的. JDK 默认提供三种类加载器: Bootstrap ClassLoader 启动类加载器:每次执行 java 命令时都会使 ...

  7. java类加载是什么意思_java 类加载机制有什么用

    展开全部 AVA类加载机制详解 "代码编译的结果从本地机器码转变为字节码,是存储格式发展的一32313133353236313431303231363533e78988e69d8331333 ...

  8. malloc开辟的空间在哪一个区间_C++进阶系列之STL(2)SGI版本空间配置器

    1.STL中的空间配置器在STL中,空间配置器分了2组,分别为一级空间配置器和二级空间配置器,但是它们都有自己各自运用的场合:一般说来,一级空间配置器一般分配的空间大于128B,二级空间配置器的分配空 ...

  9. Java的类加载机制

    jvm系列 垃圾回收基础 JVM的编译策略 GC的三大基础算法 GC的三大高级算法 GC策略的评价指标 JVM信息查看 GC通用日志解读 jvm的card table数据结构 Java类初始化顺序 J ...

最新文章

  1. 每天OnLineJudge 之 “杨辉三角 ”
  2. 如何检查Socket是否断开
  3. (28)Verilog HDL循环语句:for
  4. 多语言网站开发 不完全技术分析收录
  5. c语言中欧几里得模乘法逆元,扩展欧几里得算法同余方程模m乘法逆元详解
  6. Django学习笔记之二
  7. 什么是瑞利分布和准静态平坦衰落信道?
  8. 六大机构好评的Teradata 究竟有哪些过人之处?
  9. COLA 4.0应用架构在CSB集成平台的应用实践
  10. 第十篇:SpringBoot集成支付宝接口扫码支付
  11. 学习Hibernate框架笔记-第2天
  12. 【docker同容器下多项目curl 调用网络死循环问题记录】
  13. android_day01
  14. Dynamic DMA mapping Guide
  15. 关于twitter爬虫的总结
  16. 安装升级Exchange Server 2010 SP1补丁
  17. vs2012 打包winform程序需要安装的 软件 InstallShield2012SPRLimitedEdition.exe 下载
  18. 设计心理学中的重要概念(三)头脑中的知识与外界知识
  19. SOLIDWORKS的钛合金分析
  20. 久坐屁股疼的解决方案

热门文章

  1. 面临工业品行业的痛点,采购平台该如何解决?
  2. 高级java工程师需要会哪些知识
  3. 汽车显示屏服务器连接不上,行车记录仪能连接汽车中控显示屏吗
  4. cytoscape.js基础篇
  5. Android 跳转手机管家的自启动界面
  6. 电子合同改变传统采购方式,君子签助力政企采购更加高效便捷
  7. 面试准备FPGAor数字IC(三)-边沿检测、门控时钟、单双口RAM、亚稳态等
  8. 【渝粤题库】陕西师范大学201731教育测量与评价 作业 (专升本、高起本、高起专)
  9. 【计算机图形学】基本图形元素:直线的生成算法
  10. Matlab实现复指数,单位冲激,单位阶跃序列