大数据平台底层技术-JAVA篇-如何动态加载不同版本的 HIVE JDBC 驱动 - 一文读懂JAVA的类加载机制 1

本文涉及的产品
云原生大数据计算服务 MaxCompute,5000CU*H 100GB 3个月
云原生大数据计算服务MaxCompute,500CU*H 100GB 3个月
简介: 大数据平台底层技术-JAVA篇-如何动态加载不同版本的 HIVE JDBC 驱动 - 一文读懂JAVA的类加载机制

1 前言

大家好,我是明哥!

市场上大数据相关岗位和对应的的技能,主要分为三大类:

  • 侧重数据平台底层技术:如大数据运维工程师,大数据开发工程师,大数据架构师;
  • 侧重数据的加工和分析:如数据仓库工程师/建模工程师,数据工程师/ETL开发,数据分析师;(data engineer, data analyst)
  • 侧重数据的深度挖掘:如算法工程师,数据科学家,AI工程师;(data scientist)

image.png

其中第一大类,“侧重数据平台底层技术”,需要在底层大数据平台(如CDH/HDP/CDP/TDH等)的基础上,开发一些平台类的工具或应用,来支撑或辅助第二大类和第三大类相关从业人员的日常工作,所以需要扎实的 JAVA 或 SCALA 语言功底,也需要扎实的 LINUX 运维知识。

有鉴于此,后续笔者会推出系列文章,从大数据的角度出发,专门讲述数据平台底层技术 (分为 JAVA 篇与 LINUX 篇)。

本文是“数据平台底层技术-JAVA篇” 之一,讲述如何根据用户的具体情况,动态加载特定目录下的 JAVA 类,这在一些跨平台跨版本的大数据应用中,是一个常见的需求。(比如根据用户的具体情况,加载特定目录下特定版本的 HIVE JDBC 驱动),背后涉及到的知识是 JAVA 的类加载机制。

2 什么是类加载器

  • 类加载器,即 Class loaders, 是 JRE 的一部分,负责在 JVM 程序运行时,动态加载 JAVA 类到 JVM 内存中;
  • 通过类加载器,JVM 在运行 JAVA 程序时,不需要知道底层的文件系统和文件,即类加载器解耦了 JVM 和操作系统底层的文件系统;
  • 我们之所以说类是“动态”加载的,主要是因为运行程序时需要的所有的 JAVA 类,并不是一次性全部加载到 JVM 内存中,而是在需要哪个类时才加载那个类。

3 JRE 中主要有哪些内置的类加载器

3.1. Bootstrap Class Loader

  • bootstrap class loader 是 JVM 核心的一部分,是使用 native 代码编写的,所以在不同的平台上(如windows,linus,macos),有不同的实现;
  • Bootstrap class loader 是所有其它类加载器的父类;( 这里所说的父类,不是普通类继承关系层面上的父类);
  • Bootstrap Class Loader 负载加载 JDK 内部类,这些内部类在目录 $JAVA_HOME/jre/lib 下,比如 rt.jar;
  • Bootstrap Class Loader 打印时会显示为 null (因为其是用 native 代码实现的);

3.2 Extension Class Loader

  • extension class loader 是 bootstrap class loader 的子类;
  • extension class loader 负责加载JDK的扩展类,这些扩展类一般在目录 $JAVA_HOME/lib/ext 下,或系统变量 java.ext.dirs 指定的其它目录;

3.3. System/application Class Loader

  • system class loader, 或称为 application class loader, 是 Extensions classloader 的子类;
  • system class loader 负责加载 JAVA 应用级别的类, 这些类都在环境变量 classpath 指定的目录下,用户在提交 JAVA 程序时,可以通过命令行参数 -classpath 或 -cp 指定环境变量 classpath。

大家可以通过以下示例代码进行验证:

public void printClassLoaders() throws ClassNotFoundException {
    System.out.println("Classloader of this class:"
        + PrintClassLoader.class.getClassLoader());
    System.out.println("Classloader of Logging:"
        + Logging.class.getClassLoader());
    System.out.println("Classloader of ArrayList:"
        + ArrayList.class.getClassLoader());
}
示例输出:
Class loader of this class:sun.misc.Launcher$AppClassLoader@18b4aac2
Class loader of Logging:sun.misc.Launcher$ExtClassLoader@18769467
Class loader of ArrayList:null

4 JRE 中类加载器是怎么工作的

image.png

类加载器加载类,遵循了双亲委派模型:

  • “双亲委派模型”的双亲,更严谨来讲,其实是多级单亲;
  • 双亲委派模型下,所有的类加载器,除了 bootstrap class loader, 都有一个唯一的父加载器;
  • ”双亲委派模型“的父子关系,不是普通 JAVA 类关系层面上的父子继承关系 (extends),而更像是一种有层次之分的组合关系;
  • 当类加载器收到一个类或资源的加载请求后,会首先搜索该类或资源是否已经被加载到内存中了,如果已经被加载了的话,不会重复加载该类或资源;如果没有被加载,则会将加载请求委派给父级加载器去加载;(在内存中搜索时,一样会遵循类的可见性,类的可见性的具体含义见下文)
  • 父级加载器一样遵循向上委派机制,将加载请求委派给其父级加载器去加载,通过这种向上传导关系,所有的类加载请求,最终都会被传入到启动类加载器(Bootstrap ClassLoader)中;
  • 只有当父级加载器反馈无法完成特定类或资源的加载请求时,即父级加载器在它的搜索范围内找不到该类或资源时,子级加载器才尝试在自己的搜索范围内加载类或资源;
  • 如果所有的父级加载器在其搜索范围内都找不到该类或资源,且子级加载器在自己的搜索范围内也找不到该类或资源,JVM 就会报 java.lang.ClassNotFoundException;

遵循以上双亲委派模型,当我们需要加载一个应用类到 JVM 中时:

  • system class loader 首先搜索 jvm 内存,发现该类没有被加载过,然后会委派该加载请求给其父类加载器 extension class loader;
  • extension class loader 又会将该加载请求委派给其父类 bootstrap class loader;
  • bootstrap class loader 尝试加载该类,会在其搜索范围内(即目录 $JAVA_HOME/jre/lib)尝试查找该类,找不到;
  • 然后 extension class loader 才会尝试加载该类,在其搜索范围内(即目录 $JAVA_HOME/lib/ext 下,或系统变量 java.ext.dirs 指定的其它目录下)尝试查找该类,找不到;
  • 然后 system class loader 才会尝试自己加载该类,会在其搜索范围内(环境变量 classpath 指定的目录下)尝试查找该类,查找成功,加载成功。

通过以上双亲委派模型,JVM 确保了:

  • 类的唯一性 Unique:只有在父类加载器在其搜索范围内找不到类,无法加载类时,子类加载器才会尝试自己加载,这样就避免了重复加载,确保了类的唯一性:比如类 java.lang.Object,它在 $JAVA_HOME/jre/lib/rt.jar中,无论哪个类加载器需要加载该类,最终都是委派给处于模型顶端的启动类加载器bootstrap class loader 加载的,而 bootstrap class loader 只会在 $JAVA_HOME/jre/lib/rt.jar 中搜索加载该类,因此类 java.lang.Object 在各种加载环境中都是同一个类;
  • 类的可见性 Visibility:父类加载器加载的类对子类加载器(加载的类)可见,而子类加载器加载的类对父类加载器(加载的类)不可见:(所谓的可见,即能访问):即 system class loader 加载的类,可见 extension 和 Bootstrap class loaders 加载的类,反过来则不可见;比如,Class A 是 application class loader 加载的,而 class B 是 extensions class loader 加载的, 则此时 Application class loader 加载的其它类,可以访问 A 和 B; 而 extenson class loader 加载的其它:类, 则只可以访问 B 而不能访问A;
  • 类的安全性 Secure:双亲委派模型实现了类的唯一性和可见性,避免了用户随意定义类加载器加载核心 API 带来的安全隐患,从而进一步确保了类的安全性;

以上双亲委派模型的具体实现,可以参见源码java.lang.ClassLoader

image.png


5. JRE 中双亲委派模型的不足与 thread Context Classloaders

有时,JVM 核心类可能需要动态加载应用开发人员提供的特定类或资源:

  • 比如 JNDI 中核心功能都是在 rt.jar 的核心内部类中实现的,但在程序运行时,这些 JNDI 类可能需要加载类加载路径下各个 vendor 的具体实现类 (通过命令行参数 -classpath 或 -cp 指定类加载路径的具体目录),此时就需要 bootstrap class loader (parent class loader) 加载本应只对 application class loader 可见的类 (child class loader);
  • 再比如 JDBC 中核心功能都是在 rt.jar 的核心内部类中实现的,包括 java.sql.DriverManager/java.sql.Driver等, 但在程序运行时,这些 JDBC 类可能需要加载类加载路径下各个 vendor 的具体实现类 (通过命令行参数 -classpath 或 -cp 指定类加载路径的具体目录),比如 oracle jdbc 驱动 oracle.jdbc.driver.OracleDriver,此时也需要 bootstrap class loader (parent class loader) 加载本应只对 application class loader 可见的类 (child class loader).

此时,以上 J2SE 双亲委派机制就不 work 了。为此,JVM 提供了 thread context class loader:

  • 每个线程在创建时,都有一个对应的 ContextClassLoader,来负责该线程中类或资源的加载;
  • 当没有显示指定时,线程的 ContextClassLoader 跟其父线程的 context class loader 是同一个;
  • 可以通过方法 getContextClassLoader() 获得当前线程的 ContextClassLoader;
  • 可以通过方法 setContextClassLoader(ClassLoader cl) 重置当前线程的 ContextClassLoader;

image.png

image.png

image.png

可以通过以下代码,验证 JDBC 相关类的加载器:

Class<?> driverManagerClass = Class.forName("java.sql.DriverManager");
Class<?> driverClass = Class.forName("java.sql.Driver");
Class<?> oracleDriverClass = Class.forName("oracle.jdbc.driver.OracleDriver");
System.out.println("driverManagerClass.getClassLoader():" + driverManagerClass.getClassLoader());
System.out.println("driverClass.getClassLoader():" + driverClass.getClassLoader());
System.out.println("oracleDriverClass.getClassLoader():" + oracleDriverClass.getClassLoader());
对应的输出如下:
driverManagerClass.getClassLoader():null
drivrClass.getClassLoader():null
oracleDriverClass.getClassLoader():sun.misc.Launcher$AppClassLoader@18b4aac2


相关实践学习
基于MaxCompute的热门话题分析
本实验围绕社交用户发布的文章做了详尽的分析,通过分析能得到用户群体年龄分布,性别分布,地理位置分布,以及热门话题的热度。
SaaS 模式云数据仓库必修课
本课程由阿里云开发者社区和阿里云大数据团队共同出品,是SaaS模式云原生数据仓库领导者MaxCompute核心课程。本课程由阿里云资深产品和技术专家们从概念到方法,从场景到实践,体系化的将阿里巴巴飞天大数据平台10多年的经过验证的方法与实践深入浅出的讲给开发者们。帮助大数据开发者快速了解并掌握SaaS模式的云原生的数据仓库,助力开发者学习了解先进的技术栈,并能在实际业务中敏捷的进行大数据分析,赋能企业业务。 通过本课程可以了解SaaS模式云原生数据仓库领导者MaxCompute核心功能及典型适用场景,可应用MaxCompute实现数仓搭建,快速进行大数据分析。适合大数据工程师、大数据分析师 大量数据需要处理、存储和管理,需要搭建数据仓库?学它! 没有足够人员和经验来运维大数据平台,不想自建IDC买机器,需要免运维的大数据平台?会SQL就等于会大数据?学它! 想知道大数据用得对不对,想用更少的钱得到持续演进的数仓能力?获得极致弹性的计算资源和更好的性能,以及持续保护数据安全的生产环境?学它! 想要获得灵活的分析能力,快速洞察数据规律特征?想要兼得数据湖的灵活性与数据仓库的成长性?学它! 出品人:阿里云大数据产品及研发团队专家 产品 MaxCompute 官网 https://www.aliyun.com/product/odps&nbsp;
相关文章
|
21天前
|
存储 机器学习/深度学习 分布式计算
大数据技术——解锁数据的力量,引领未来趋势
【10月更文挑战第5天】大数据技术——解锁数据的力量,引领未来趋势
|
8天前
|
存储 分布式计算 数据可视化
大数据常用技术与工具
【10月更文挑战第16天】
40 4
|
21天前
|
存储 数据采集 监控
大数据技术:开启智能决策与创新服务的新纪元
【10月更文挑战第5天】大数据技术:开启智能决策与创新服务的新纪元
|
21天前
|
存储 分布式计算 druid
大数据-149 Apache Druid 基本介绍 技术特点 应用场景
大数据-149 Apache Druid 基本介绍 技术特点 应用场景
46 1
大数据-149 Apache Druid 基本介绍 技术特点 应用场景
|
12天前
|
Java 关系型数据库 MySQL
mysql5.7 jdbc驱动
遵循上述步骤,即可在Java项目中高效地集成MySQL 5.7 JDBC驱动,实现数据库的访问与管理。
21 1
|
2月前
|
机器学习/深度学习 运维 分布式计算
大数据技术专业就业前景
大数据技术专业就业前景广阔,广泛应用于互联网、金融、医疗等众多行业,助力企业数字化转型。岗位涵盖大数据开发、分析、运维及管理,如大数据工程师、分析师和系统运维工程师等。这些岗位因专业性和稀缺性而享有优厚薪资,尤其在一线城市可达20万至50万年薪。随着技术进步和经验积累,从业者可晋升为高级职位或投身数据咨询、创业等领域,发展空间巨大。
47 5
|
21天前
|
存储 数据采集 分布式计算
大数据技术:开启智能时代的新引擎
【10月更文挑战第5天】大数据技术:开启智能时代的新引擎
|
22天前
|
SQL 分布式计算 Java
大数据-96 Spark 集群 SparkSQL Scala编写SQL操作SparkSQL的数据源:JSON、CSV、JDBC、Hive
大数据-96 Spark 集群 SparkSQL Scala编写SQL操作SparkSQL的数据源:JSON、CSV、JDBC、Hive
23 0
|
22天前
|
SQL 分布式计算 关系型数据库
Hadoop-24 Sqoop迁移 MySQL到Hive 与 Hive到MySQL SQL生成数据 HDFS集群 Sqoop import jdbc ETL MapReduce
Hadoop-24 Sqoop迁移 MySQL到Hive 与 Hive到MySQL SQL生成数据 HDFS集群 Sqoop import jdbc ETL MapReduce
61 0
|
6月前
|
SQL 数据采集 数据挖掘
大数据行业应用之Hive数据分析航班线路相关的各项指标
大数据行业应用之Hive数据分析航班线路相关的各项指标
185 1