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

简介: 大数据平台底层技术-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的热门话题分析
Apsara Clouder大数据专项技能认证配套课程:基于MaxCompute的热门话题分析
相关文章
|
5月前
|
监控 Cloud Native Java
Quarkus 云原生Java框架技术详解与实践指南
本文档全面介绍 Quarkus 框架的核心概念、架构特性和实践应用。作为新一代的云原生 Java 框架,Quarkus 旨在为 OpenJDK HotSpot 和 GraalVM 量身定制,显著提升 Java 在容器化环境中的运行效率。本文将深入探讨其响应式编程模型、原生编译能力、扩展机制以及与微服务架构的深度集成,帮助开发者构建高效、轻量的云原生应用。
625 44
|
5月前
|
安全 Java API
Java Web 在线商城项目最新技术实操指南帮助开发者高效完成商城项目开发
本项目基于Spring Boot 3.2与Vue 3构建现代化在线商城,涵盖技术选型、核心功能实现、安全控制与容器化部署,助开发者掌握最新Java Web全栈开发实践。
561 1
|
4月前
|
存储 人工智能 大数据
云栖2025|阿里云开源大数据发布新一代“湖流一体”数智平台及全栈技术升级
阿里云在云栖大会发布“湖流一体”数智平台,推出DLF-3.0全模态湖仓、实时计算Flink版升级及EMR系列新品,融合实时化、多模态、智能化技术,打造AI时代高效开放的数据底座,赋能企业数字化转型。
1000 0
|
6月前
|
安全 Java 编译器
new出来的对象,不一定在堆上?聊聊Java虚拟机的优化技术:逃逸分析
逃逸分析是一种静态程序分析技术,用于判断对象的可见性与生命周期。它帮助即时编译器优化内存使用、降低同步开销。根据对象是否逃逸出方法或线程,分析结果分为未逃逸、方法逃逸和线程逃逸三种。基于分析结果,编译器可进行同步锁消除、标量替换和栈上分配等优化,从而提升程序性能。尽管逃逸分析计算复杂度较高,但其在热点代码中的应用为Java虚拟机带来了显著的优化效果。
202 4
|
4月前
|
数据可视化 大数据 关系型数据库
基于python大数据技术的医疗数据分析与研究
在数字化时代,医疗数据呈爆炸式增长,涵盖患者信息、检查指标、生活方式等。大数据技术助力疾病预测、资源优化与智慧医疗发展,结合Python、MySQL与B/S架构,推动医疗系统高效实现。
|
5月前
|
安全 架构师 Java
Java LTS版本进化秀:从8到21的欢乐升级之旅
困惑于Java版本选择?轻松幽默地穿越Java LTS版本时光隧道,掌握从Java 8到21的关键特性。通过一家初创公司的系统升级故事,直观了解每个版本如何解决代码冗余、性能瓶颈等开发痛点,助你在技术选型中做出明智决策。
324 7
|
6月前
|
Java 测试技术 API
2025 年 Java 开发者必知的最新技术实操指南全览
本指南涵盖Java 21+核心实操,详解虚拟线程、Spring Boot 3.3+GraalVM、Jakarta EE 10+MicroProfile 6微服务开发,并提供现代Java开发最佳实践,助力开发者高效构建高性能应用。
947 4
|
5月前
|
安全 Cloud Native Java
Java 模块化系统(JPMS)技术详解与实践指南
本文档全面介绍 Java 平台模块系统(JPMS)的核心概念、架构设计和实践应用。作为 Java 9 引入的最重要特性之一,JPMS 为 Java 应用程序提供了强大的模块化支持,解决了长期存在的 JAR 地狱问题,并改善了应用的安全性和可维护性。本文将深入探讨模块声明、模块路径、访问控制、服务绑定等核心机制,帮助开发者构建更加健壮和可维护的 Java 应用。
409 0
|
5月前
|
机器学习/深度学习 传感器 分布式计算
数据才是真救命的:聊聊如何用大数据提升灾难预警的精准度
数据才是真救命的:聊聊如何用大数据提升灾难预警的精准度
381 14
|
6月前
|
机器学习/深度学习 运维 监控
运维不怕事多,就怕没数据——用大数据喂饱你的运维策略
运维不怕事多,就怕没数据——用大数据喂饱你的运维策略
302 0