当前位置:首页 > java技术文章 > Java的ThreadContext类加载器的实现

Java的ThreadContext类加载器的知识点总结

  • 发布时间:
  • 作者:码农之家原创
  • 点击:185

这篇文章主要知识点是关于java、类加载器、ThreadContext、的内容,如果大家想对相关知识点有系统深入的学习,可以参阅以下电子书

深入理解Java虚拟机:JVM高级特性与最佳实践
  • 类型:Java虚拟机大小:253 MB格式:PDF作者:周志明
立即下载

Java的ThreadContext类加载器的实现

疑惑

以前在看源码的时候,总是会遇到框架里的代码使用Thread.currentThread.getContextClassLoader()获取当前线程的Context类加载器,通过这个Context类加载器去加载类。

我们平时在程序中写代码的时候,遇到要动态加载类的时候,一般使用Class.forName()的方式加载我们需要的类。比如最常见的,当我们进行JDBC编程的时候,我们通过Class.forName()去加载JDBC的驱动。

try {
 return Class.forName("oracle.jdbc.driver.OracleDriver");
} catch (ClassNotFoundException e) {
 // skip
}

那么为什么当我们使用Class.forName()的方式去加载类的时候,如果类找不到,我们还要尝试用Thread.currentThread.getContextLoader()获取的类加载器去加载类呢?比如我们可能会碰到下面这种代码:

try {
 return Class.forName(className);
} catch (ClassNotFoundException e) {
 // skip
}

ClassLoader ctxClassLoader = Thread.currentThread().getContextClassLoader();
if (ctxClassLoader != null) {
 try {
 clazz = ctxClassLoader.loadClass(className);
 } catch (ClassNotFoundException e) {
   // skip
 }
}

这里加粗的部分,就是使用了Thread.currentThread.getContextLoader()获取的加载器去加载类。显然,Class.forName()加载类的时候使用的类加载器可能和Thread.currentThread.getContextLoader()获取的类加载器是不同的。那么为什么会出现不同呢?

JAVA的类加载器

要理解为什么会用到Thread.currentThread.getContextLoader()获取的这个类加载器之前,我们先来了解下JVM里使用的类加载器(ClassLoader)。

JVM默认有三种类加载器:

  1. Bootstrap Class Loader
  2. Extension Class Loader
  3. System Class Loader

Bootstrap Class Loader

Bootstrap Class Loader类加载器是JDK自带的一款类加载器,用于加载JDK内部的类。Bootstrap类加载器用于加载JDK中$JAVA_HOME/jre/lib下面的那些类,比如rt.jar包里面的类。Bootstrap类加载器是JVM的一部分,一般采用native代码编写。

Extension Class Loader

  Extension Class Loader类加载器主要用于加载JDK扩展包里的类。一般$JAVA_HOME/lib/ext下面的包都是通过这个类加载器加载的,这个包下面的类基本上是以javax开头的。

System Class Loader

System Class Loader类加载器也叫应用程序类加载器(AppClassLoader)。顾名思义,这个类加载器就是用来加载开发人员自己平时写的应用代码的类的。System类加载器是用于加载存放在classpath路径下的那些应用程序级别的类的。

下面的代码列举出了这三个类加载器:

public class MainClass {
 public static void main(String[] args) {
  System.out.println(Integer.class.getClassLoader());
  System.out.println(Logging.class.getClassLoader());
  System.out.println(MainClass.class.getClassLoader());
 }
}

其中获取Bootstrap类加载器永远返回null值

null # Bootstrap类加载器
sun.misc.Launcher$ExtClassLoader@5e2de80c # Extension类加载器
sun.misc.Launcher$AppClassLoader@18b4aac2 # System类加载器

双亲委派模型

上面介绍的三种类加载器,并不是孤立的,他们之间有一个层次关系:

Java的ThreadContext类加载器的实现

三个类加载器之间通过这个层次关系协同工作,一起负责类的加载工作。上面的这种层次模型称为类加载器的“双亲委派”模型。双亲委派模型要求,除了最顶层的Bootstrap类加载器之外,所有的类加载器都必须有一个parent加载器。当类加载器加载类的时候,首先检查缓存中是否有已经被加载的类。如果没有,则优先委托它的父加载器去加载这个类,父加载器执行和前面子加载器一样的工作,直到请求达到顶层的Bootstrap类加载器。如果父加载器不能加载需要的类,那么这个时候才会让子加载器自己去尝试加载这个类。工作原理类似于下面这种方式:

  Java的ThreadContext类加载器的实现

我们可以通过JDK里ClassLoader里面的代码一窥双亲委派机制的实现方式,代码实现在ClassLoader.loadClass()里面

rotected Class<?> loadClass(String name, boolean resolve)
 throws ClassNotFoundException

 synchronized (getClassLoadingLock(name)) {
  // First, check if the class has already been loaded
  Class<?> c = findLoadedClass(name);
  if (c == null) {
   long t0 = System.nanoTime();
   try {
    if (parent != null) {
     c = parent.loadClass(name, false);
    } else {
     c = findBootstrapClassOrNull(name);
    }
   } catch (ClassNotFoundException e) {
    // ClassNotFoundException thrown if class not found
    // from the non-null parent class loader
   }

   if (c == null) {
    // If still not found, then invoke findClass in order
    // to find the class.
    long t1 = System.nanoTime();
    c = findClass(name);

    // this is the defining class loader; record the stats
    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
    sun.misc.PerfCounter.getFindClasses().increment();
   }
  }
  if (resolve) {
   resolveClass(c);
  }
  return c;
 }

采用双亲委派的方式组织类加载器,一个好处是为了安全。如果我们自己定义了一个String类,希望将这个String类替换掉默认Java中的java.lang.String的实现。

我们将自己实现的String类的class文件放到classpath路径下,当我们使用类加载器去加载我们实现的String类的时候,首先,类加载器会将请求委托给父加载器,通过层层委派,最终由Bootstrap类加载器加载rt.jar包里的String类型,然后一路返回给我们。在这个过程中,我们的类加载器忽略掉了我们放在classpath中自定义的String类。

如果没有采用双亲委派机制,那么System类加载器可以在classpath路径中找到String的class文件并加载到程序中,导致JDK中的String实现被覆盖。所以类加载器的这种工作方式,在一定程度上保证了Java程序可以安全稳定的运行。

线程上下文类加载器

上面讲了那么多类加载器相关的内容,可还是没有讲到今天的主题,线程上下文类加载器。

到这里,我们已经知道Java提供了三种类加载器,并且按照严格的双亲委派机制协同工作。表面上,似乎很完美,但正是这种严格的双亲委派机制导致在加载类的时候,存在一些局限性。

当我们更加基础的框架需要用到应用层面的类的时候,只有当这个类是在我们当前框架使用的类加载器可以加载的情况下我们才能用到这些类。换句话说,我们不能使用当前类加载器的子加载器加载的类。这个限制就是双亲委派机制导致的,因为类加载请求的委派是单向的。

虽然这种情况不多,但是还是会有这种需求。比较典型的,JNDI服务。JNDI提供了查询资源的接口,但是具体实现由不同的厂商实现。这个时候,JNDI的代码是由JVM的Bootstrap类加载器加载,但是具体的实现是用户提供的JDK之外的代码,所以只能由System类加载器或者其他用户自定义的类加载器去加载,在双亲委派的机制下,JNDI获取不到JNDI的SPI的实现。

为了解决这个问题,引入了线程上下文类加载器。通过java.lang.Thread类的setContextClassLoader()设置当前线程的上下文类加载器(如果没有设置,默认会从父线程中继承,如果程序没有设置过,则默认是System类加载器)。有了线程上下文类加载器,应用程序就可以通过java.lang.Thread.setContextClassLoader()将应用程序使用的类加载器传递给使用更顶层类加载器的代码。比如上面的JNDI服务,就可以利用这种方式获取到可以加载SPI实现的类加载器,获取需要的SPI实现类。

Java的ThreadContext类加载器的实现

可以看到,引入线程类加载器实际是对双亲委派机制的破坏,但是却提供了类加载的灵活性。

解惑

回到开头,框架的代码为了加载框架之外用户实现的类,由于这些类可能没法通过框架使用的类加载器进行加载,为了绕过类加载器的双亲委派模型,采用Thread.getContextClassLoader()的方式去加载这些类。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持码农之家。

以上就是本次给大家分享的关于java的全部知识点内容总结,大家还可以在下方相关文章里找到相关文章进一步学习,感谢大家的阅读和支持。

推荐内容

idea2020注册激活码(激活到2100年)

实例分析Java实现的zip压缩及解压缩工具类

python3 pandas 如何读取MySQL数据和插入

ThinkPHP3.2.3框架如何实现分页功能

深入理解JS函数stack size计算方法

展开 +

收起 -

Java 相关电子书
学习笔记
网友NO.940592

Java中类加载过程全面解析

类文件加载的顺序 1、先加载执行父类的静态变量及静态初始化块(执行先后顺序按排列的先后顺序) 2、再加载执行本类的静态变量及静态初始化块 只要类没有被销毁,静态变量及静态初始化块只会执行1次,后续再对该类进行其他操作也不会再执行这两个步骤。 类实例创建过程 只有在调用new方法时才会创建类的实例 1、按照上面类文件加载的顺序(类已被加载则跳过此步) 2、父类的非静态变量及非静态初始化块 3、父类的构造方法 4、本类的非静态变量及非静态初始化块 5、本类的构造方法 4、类实例销毁时候,首先销毁子类部分,再销毁父类部分 静态方法和非静态方法都是被动调用 即系统不会自动调用执行。所以用户没有调用时都不执行,主要区别在于静态方法可以直接用类名直接调用(实例化对象也可以),而非静态方法只能先实例化对象后才能调……

网友NO.795384

浅谈JVM核心之JVM运行和类加载

前言 本篇博客将写一点关于JVM的东西,涉及JVM运行时数据区、类加载的过程、类加载器、ClassLoader、双亲委派机制、自定义类加载器等,这些都是博主自己的一点理解,如果有误,欢迎大家评论拍砖~ 关于JVM运行时数据区 JVM运行时数据区 关于类加载 class文件加载至内存,链接(校验、解析),初始化;最终形成JVM可以直接使用的JAVA类型的过程。 加载:在方法区形成类的运行时数据结构;在堆里面形成该类的Class对象,作为访问方法区的入口。 加载 链接:class文件是否存在问题;一些符号引号替换成直接引用。 初始化:初始化一个类,先初始化它的父类。虚拟机会保证一个类的初始化在多线程环境中被正确加锁和同步。 要使用类A,必须先加载类A;加载类A,就会把静态变量、静态块合并初始化,然后在调用构造器。注意类的加载和初始化,只有一次……

网友NO.322710

classloader类加载器_基于java类的加载方式详解

基础概念 Classloader 类加载器,用来加载 Java 类到 Java 虚拟机中。与普通程序不同的是。Java程序(class文件)并不是本地的可执行程序。当运行Java程序时,首先运行JVM(Java虚拟机),然后再把Java class加载到JVM里头运行,负责加载Java class的这部分就叫做Class Loader。 JVM本身包含了一个ClassLoader称为Bootstrap ClassLoader,和JVM一样,BootstrapClassLoader是用本地代码实现的,它负责加载核心JavaClass(即所有java.*开头的类)。另外JVM还会提供两个ClassLoader,它们都是用Java语言编写的,由BootstrapClassLoader加载;其中Extension ClassLoader负责加载扩展的Javaclass(例如所有javax.*开头的类和存放在JRE的ext目录下的类),ApplicationClassLoader负责加载应用程序自身的类。 当运行一个程序的时候,JVM启动,运行bootstrapclassloader,该ClassLoader加载java核心API(ExtClassLoader和AppClassLoader也在此时被加载),然后调……

网友NO.560990

java用类加载器的5种方式读取.properties文件

用类加载器的5中形式读取.properties文件(这个.properties文件一般放在src的下面) 用类加载器进行读取:这里采取先向大家讲读取类加载器的几种方法;然后写一个例子把几种方法融进去,让大家直观感受。最后分析原理。(主要是结合所牵涉的方法的源代码的角度进行分析) 这里先介绍用类加载器读取的几种方法: 1.任意类名.class.getResourceAsStream("/文件所在的位置");【文件所在的位置从包名开始写】 2.和.properties文件在同一个目录下的类名.class.getResourceAsStream("文件所在的位置");【文件所在的位置从包名开始写,注意这里和上面的相比较少了一个斜杠/】 当然你也可以写成跟1一样的形式即:任意类名.class.getResourceAsStream("/文件所在的位置"); 3.任意类名.class.getClassLoader().getResourceAsStream("文件所在的位置");【文件所在的位置从包名开始写】 4.任意类名.class.……

网友NO.203088

JVM的类加载过程以及双亲委派模型详解

jvm 的主要组成部分 类加载器(ClassLoader) 运行时数据区(Runtime Data Area) 执行引擎(Execution Engine) 本地库接口(Native Interface) jvm 运行时数据区的组成 方法区: ①方法区主要用来存储已被虚拟机加载的类信息(构造器,接口定义)、常量、静态变量和运行时常量池等数据。 ②该区域是被线程共享的。 ③方法区里有一个运行时常量池,用于存放静态编译产生的字面量和符号引用。该常量池具有动态性,也就是说常量并不一定是编译时确定,运行时生成的常量也会存在这个常量池中。 虚拟机栈: 虚拟机栈也叫栈内存,主管Java程序的运行,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束该栈就Over,生命周期和线程一致,是线程私有的。 8种基本类型的变量+对象的引……

<
1
>

电子书 编程教程 文档 软件 源码 视频

Copyright 2018-2020 xz577.com 码农之家

本站所有电子书资源不再提供下载地址,只分享来路

免责声明:网站所有作品均由会员网上搜集共同更新,仅供读者预览及学习交流使用,下载后请24小时内删除

版权投诉 / 书籍推广 / 赞助:QQ:520161757