热门标签 | HotTags
当前位置:  开发笔记 > 前端 > 正文

深入解析Java中的Classloader的运行机制

这篇文章主要介绍了Java中的Classloader的运行机制,包括从JVM方面讲解类加载器的委托机制等,需要的朋友可以参考下

java有两种类型的classload,一种是user-defined的,一种是jvm内置的bootstrap class loader,所有user-defined的class loader都是java.lang.ClassLoader的子类.

而jvm内置的class loader有3种,分别是 Bootstrap ClassLoader, Extension ClassLoader(即ExtClassLoader),System ClassLoader(即AppClassLoader).

而jvm加载时的双亲委派 就不说了,javaeye上有很多文章都有介绍..
可以分别看一下他们的构造器,其中Bootstrap ClassLoader是用c写的.
java.lang.ClassLoader

  protected ClassLoader(ClassLoader parent) {
 SecurityManager security = System.getSecurityManager();
 if (security != null) {
   security.checkCreateClassLoader();
 }
//给父loader赋值.
 this.parent = parent;
 initialized = true;
  }
  protected ClassLoader() {
 SecurityManager security = System.getSecurityManager();
 if (security != null) {
   security.checkCreateClassLoader();
 }
//这边将会把AppClassLoader付给父loader
 this.parent = getSystemClassLoader();
 initialized = true;
  }


这个构造器有带参数的,和不带构造器的2个。带参数的构造器传入的是这个 class loader的父loader,而不带参数的构造器则会把getSystemClassLoader()所返回的class loader当作他自己的父装载器.下面我们来看getSystemClassLoader()的代码

public static ClassLoader getSystemClassLoader() {
//所返回的class loader在这个方法里面被赋值
 initSystemClassLoader();
 if (scl == null) {
   return null;
 }
 SecurityManager sm = System.getSecurityManager();
 if (sm != null) {
   ClassLoader ccl = getCallerClassLoader();
   if (ccl != null && ccl != scl && !scl.isAncestor(ccl)) {
 sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);
   }
 }
 return scl;
  }

  private static synchronized void initSystemClassLoader() {
 if (!sclSet) {
   if (scl != null)
 throw new IllegalStateException("recursive invocation");
      sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
   if (l != null) {
 Throwable oops = null;
//在这边被赋值
 scl = l.getClassLoader();
     ...................................
.......................................
 }
   }
   sclSet = true;
 }
  }

这边的父class loader就是scl,也就是l.getClassLoader()所得到的,getClassLoader(),接下来看Launcher的源码:
private static Launcher launcher = new Launcher();

  public static Launcher getLauncher() {
 return launcher;
  }

  private ClassLoader loader;

  public Launcher() {
 // Create the extension class loader
 ClassLoader extcl;
 try {
//这里传入构造器的parent为空
   extcl = ExtClassLoader.getExtClassLoader();
 } catch (IOException e) {
   throw new InternalError(
 "Could not create extension class loader");
 }

 // Now create the class loader to use to launch the application
 try {
//这边可以看到默认的loader就是AppClassLoader,也就是说getSystemClassLoader返回的就是AppClassLoader
   loader = AppClassLoader.getAppClassLoader(extcl);
 } catch (IOException e) {
   throw new InternalError(
 "Could not create application class loader");
 }
//每一个当前线程一个classload,以防止多线程中的classload引起的混乱(这个是我自己理解的,呵呵)
 // Also set the context class loader for the primordial thread.
 Thread.currentThread().setContextClassLoader(loader);

 ...................................
................................................
  }

  /*
   * Returns the class loader used to launch the main application.
   */
  public ClassLoader getClassLoader() {
 return loader;
  }

从中我们看到AppClassLoader的父loader是ExtClassLoader,而ExtClassLoader的父loader是什么呢?我们在来看ExtClassLoader的构造器:

public ExtClassLoader(File[] dirs) throws IOException {
   super(getExtURLs(dirs), null, factory);
   this.dirs = dirs;
 }

他的父loader为空,而他的顶级父类是java.lang.ClassLoader而当传入的parent为null时,我们使用 ExtClassLoader load一个类时,系统会调用Bootstrap ClassLoader.

protected synchronized Class<&#63;> loadClass(String name, boolean resolve)
 throws ClassNotFoundException
  {
 // First, check if the class has already been loaded
 Class c = findLoadedClass(name);
 if (c == null) {
   try {
 if (parent != null) {
//先调用父loader来load.
   c = parent.loadClass(name, false);
 } else {
//调用Bootstrap ClassLoader来load
   c = findBootstrapClass0(name);
 }
   } catch (ClassNotFoundException e) {
     // If still not found, then invoke findClass in order
     // to find the class.
     c = findClass(name);
   }
 }
 if (resolve) {
   resolveClass(c);
 }
 return c;
  }

而这里findBootstrapClass0也就是调用Bootstrap ClassLoader这个最核心的class loader来load class.

最终我们可以看到getSystemClassLoader() 返回的class loader就是AppClassLoader.

Java Classloader机制解析

JDK默认ClassLoader
JDK 默认提供了如下几种ClassLoader

Bootstrp loader
Bootstrp加载器是用C++语言写的,它是在Java虚拟机启动后初始化的,它主要负责加载%JAVA_HOME%/jre/lib,-Xbootclasspath参数指定的路径以及%JAVA_HOME%/jre/classes中的类。

ExtClassLoader 
Bootstrp loader加载ExtClassLoader,并且将ExtClassLoader的父加载器设置为Bootstrp loader.ExtClassLoader是用Java写的,具体来说就是 sun.misc.Launcher$ExtClassLoader,ExtClassLoader主要加载%JAVA_HOME%/jre/lib/ext,此路径下的所有classes目录以及java.ext.dirs系统变量指定的路径中类库。

AppClassLoader
Bootstrp loader加载完ExtClassLoader后,就会加载AppClassLoader,并且将AppClassLoader的父加载器指定为 ExtClassLoader。AppClassLoader也是用Java写成的,它的实现类是 sun.misc.Launcher$AppClassLoader,另外我们知道ClassLoader中有个getSystemClassLoader方法,此方法返回的正是AppclassLoader.AppClassLoader主要负责加载classpath所指定的位置的类或者是jar文档,它也是Java程序默认的类加载器。

综上所述,它们之间的关系可以通过下图形象的描述:

2015111185228919.png (447×234)

双亲委托模型
Java中ClassLoader的加载采用了双亲委托机制,采用双亲委托机制加载类的时候采用如下的几个步骤:

当前ClassLoader首先从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。

每个类加载器都有自己的加载缓存,当一个类被加载了以后就会放入缓存,等下次加载的时候就可以直接返回了。

当前classLoader的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载,一直到bootstrp ClassLoader.

当所有的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它自己的缓存中,以便下次有加载请求的时候直接返回。

说到这里大家可能会想,Java为什么要采用这样的委托机制?理解这个问题,我们引入另外一个关于Classloader的概念“命名空间”, 它是指要确定某一个类,需要类的全限定名以及加载此类的ClassLoader来共同确定。也就是说即使两个类的全限定名是相同的,但是因为不同的 ClassLoader加载了此类,那么在JVM中它是不同的类。明白了命名空间以后,我们再来看看委托模型。采用了委托模型以后加大了不同的 ClassLoader的交互能力,比如上面说的,我们JDK本生提供的类库,比如hashmap,linkedlist等等,这些类由bootstrp 类加载器加载了以后,无论你程序中有多少个类加载器,那么这些类其实都是可以共享的,这样就避免了不同的类加载器加载了同样名字的不同类以后造成混乱。

如何自定义ClassLoader
Java除了上面所说的默认提供的classloader以外,它还容许应用程序可以自定义classloader,那么要想自定义classloader我们需要通过继承java.lang.ClassLoader来实现,接下来我们就来看看再自定义Classloader的时候,我们需要注意的几个重要的方法:

1.loadClass 方法

loadClass method declare

public Class<&#63;> loadClass(String name) throws ClassNotFoundException

上面是loadClass方法的原型声明,上面所说的双亲委托机制的实现其实就实在此方法中实现的。下面我们就来看看此方法的代码来看看它到底如何实现双亲委托的。

loadClass method implement

public Class<&#63;> loadClass(String name) throws ClassNotFoundException
 { 
return loadClass(name, false);
}

从上面可以看出loadClass方法调用了loadcClass(name,false)方法,那么接下来我们再来看看另外一个loadClass方法的实现。

Class loadClass(String name, boolean resolve)

protected synchronized Class<&#63;> loadClass(String name, boolean resolve) throws ClassNotFoundException  
 { // First, check if the class has already been loaded Class c = findLoadedClass(name);
//检查class是否已经被加载过了 if (c == null)
 {   
 try {   
if (parent != null) {     
 c = parent.loadClass(name, false); //如果没有被加载,且指定了父类加载器,则委托父加载器加载。  
 } else {    
 c = findBootstrapClass0(name);//如果没有父类加载器,则委托bootstrap加载器加载   } 
   } catch (ClassNotFoundException e) {     
 // If still not found, then invoke findClass in order     
// to find the class.     
 c = findClass(name);//如果父类加载没有加载到,则通过自己的findClass来加载。   } 
 } 
 if (resolve) 
{   
 resolveClass(c); 
 } 
return c;
}

上面的代码,我加了注释通过注释可以清晰看出loadClass的双亲委托机制是如何工作的。 这里我们需要注意一点就是public Class<&#63;> loadClass(String name) throws ClassNotFoundException没有被标记为final,也就意味着我们是可以override这个方法的,也就是说双亲委托机制是可以打破的。另外上面注意到有个findClass方法,接下来我们就来说说这个方法到底是搞末子的。

2.findClass

我们查看java.lang.ClassLoader的源代码,我们发现findClass的实现如下:

protected Class<&#63;> findClass(String name) throws ClassNotFoundException
 { 
throw new ClassNotFoundException(name);
}

我们可以看出此方法默认的实现是直接抛出异常,其实这个方法就是留给我们应用程序来override的。那么具体的实现就看你的实现逻辑了,你可以从磁盘读取,也可以从网络上获取class文件的字节流,获取class二进制了以后就可以交给defineClass来实现进一步的加载。defineClass我们再下面再来描述。 ok,通过上面的分析,我们可以得出如下结论:

我们在写自己的ClassLoader的时候,如果想遵循双亲委托机制,则只需要override findClass.

3.defineClass

我们首先还是来看看defineClass的源码:

defineClass

protected final Class<&#63;> defineClass(String name, byte[] b, int off, int len) 
throws ClassFormatError
{   
 return defineClass(name, b, off, len, null);
}

从上面的代码我们看出此方法被定义为了final,这也就意味着此方法不能被Override,其实这也是jvm留给我们的唯一的入口,通过这个唯 一的入口,jvm保证了类文件必须符合Java虚拟机规范规定的类的定义。此方法最后会调用native的方法来实现真正的类的加载工作。

Ok,通过上面的描述,我们来思考下面一个问题:
假如我们自己写了一个java.lang.String的类,我们是否可以替换调JDK本身的类?

答案是否定的。我们不能实现。为什么呢?我看很多网上解释是说双亲委托机制解决这个问题,其实不是非常的准确。因为双亲委托机制是可以打破的,你完全可以自己写一个classLoader来加载自己写的java.lang.String类,但是你会发现也不会加载成功,具体就是因为针对java.*开头的类,jvm的实现中已经保证了必须由bootstrp来加载。

不遵循“双亲委托机制”的场景
上面说了双亲委托机制主要是为了实现不同的ClassLoader之间加载的类的交互问题,被大家公用的类就交由父加载器去加载,但是Java中确实也存在父类加载器加载的类需要用到子加载器加载的类的情况。下面我们就来说说这种情况的发生。

Java中有一个SPI(Service Provider Interface)标准,使用了SPI的库,比如JDBC,JNDI等,我们都知道JDBC需要第三方提供的驱动才可以,而驱动的jar包是放在我们应 用程序本身的classpath的,而jdbc 本身的api是jdk提供的一部分,它已经被bootstrp加载了,那第三方厂商提供的实现类怎么加载呢?这里面JAVA引入了线程上下文类加载的概 念,线程类加载器默认会从父线程继承,如果没有指定的话,默认就是系统类加载器(AppClassLoader),这样的话当加载第三方驱动的时候,就可 以通过线程的上下文类加载器来加载。
另外为了实现更灵活的类加载器OSGI以及一些Java app server也打破了双亲委托机制。


推荐阅读
  • 本文探讨了2019年前端技术的发展趋势,包括工具化、配置化和泛前端化等方面,并提供了详细的学习路线和职业规划建议。 ... [详细]
  • Asp.net MVC 中 Bundle 配置详解:合并与压缩 JS 和 CSS 文件
    本文深入探讨了 Asp.net MVC 中如何利用 Bundle 功能来合并和压缩 JavaScript 和 CSS 文件,提供了详细的配置步骤和示例代码,适合开发人员参考学习。 ... [详细]
  • PHP中静态类与静态变量的应用差异探讨
    本文深入探讨了PHP编程语言中静态类与静态变量的具体应用及其差异性,旨在帮助开发者更好地理解和运用这些概念,以提升代码质量和效率。 ... [详细]
  • Web前端开发中Webpack项目的实用技巧总结
    本文探讨了在使用Webpack构建前端项目时的一些实用技巧,包括如何高效地使用移动端UI框架Mint UI和MUI,以及如何优化项目性能和用户体验。 ... [详细]
  • 如何解决PHP中时间获取不准确的问题
    本文探讨了在PHP开发过程中遇到的时间获取错误问题,并提供了详细的解决方案,包括通过修改配置文件和编程方法来调整时区设置。 ... [详细]
  • Consul 单节点与集群环境构建指南
    本文详细介绍了如何安装和配置 Consul 以支持服务注册与发现、健康检查等功能,包括单节点和集群环境的搭建步骤。 ... [详细]
  • 使用Bootstrap创建响应式渐变固定头部导航栏的方法
    本文详细介绍了如何利用Bootstrap框架构建一个具有渐变效果的固定顶部响应式导航栏,包括HTML结构、CSS样式以及JavaScript交互的完整实现过程。适合前端开发者和学习者参考。 ... [详细]
  • 深入理解Java类加载机制
    本文详细探讨了Java虚拟机(JVM)中类加载器的工作原理,特别是如何通过类的全限定名从外部源获取二进制字节流,以及不同类型的类加载器及其在双亲委派模型中的角色。 ... [详细]
  • 深入解析JVM:类加载子系统详解
    本文旨在深入探讨Java虚拟机(JVM)中的类加载子系统,包括其基本结构、类加载器的工作原理、类的加载过程以及双亲委派机制。通过对这些关键点的详细分析,帮助读者更好地理解和掌握JVM的核心机制。 ... [详细]
  • 本文介绍了如何在Angular CLI创建的项目中安装并配置Bootstrap,包括必要的依赖项jQuery和Popper.js的安装步骤。 ... [详细]
  • PHP 中服务器变量的配置指南
    本文详细介绍了在 PHP 环境下如何正确设置服务器变量的方法,包括变量类型的动态转换及其应用场景。适合初学者及进阶开发者阅读。 ... [详细]
  • Bootstrap与Layui的主要差异分析
    在前端开发领域,Bootstrap与Layui是两种非常流行的框架选择。本文将深入探讨这两种框架的主要区别,帮助开发者根据项目需求做出最佳选择。 ... [详细]
  • 构建Filebeat-Kafka-Logstash-ElasticSearch-Kibana日志收集体系
    本文介绍了如何使用Filebeat、Kafka、Logstash、ElasticSearch和Kibana构建一个高效、可扩展的日志收集与分析系统。各组件分别承担不同的职责,确保日志数据能够被有效收集、处理、存储及可视化。 ... [详细]
  • Netty基础教程:构建简易Netty客户端与服务器
    Java NIO是解决传统阻塞I/O问题的关键技术之一,但其复杂性给开发者带来了挑战。Netty作为一个成熟的网络编程框架,极大地简化了这一过程。本文将通过一个简单的示例,介绍如何使用Netty创建基本的客户端和服务器。 ... [详细]
  • 本文探讨了Jeddict工具的应用价值,特别是在快速构建和部署CRUD服务系统方面的能力。通过介绍其核心功能和优势,以及当前的使用状况,文章还展望了Jeddict未来的改进方向。 ... [详细]
author-avatar
SADFGHJKSADFV_565
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有