热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Java类加载器(一)——类加载器层次与模型

类加载器  虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个

类加载器

  虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。


类加载器层次(等级)

  从JVM的角度来讲,只存在两种不同的类加载器。
  第一类是启动类加载器(Bootstrap ClassLoader):这个类加载器主要加载JVM自身工作需要的类。这个类加载器由C++语言实现(特指HotSpot),是虚拟机自身的一部分。负责将存放在%JAVA_HOME%\lib目录中的,或者被-Xbootclasspath参数所指定的路径中,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)类加载到虚拟机内存中。
  另一类就是所有其他的类加载器,这些加载器都是由java实现,独立于虚拟机外部。
  Extension ClassLoader:这个类即在其有sun.misc.Launcher$ExtClassLoader实现,它负责加载%JAVA_HOME%\lib\ext目录中,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
  Application ClassLoader:这个类加载器由sun.misc.Launcher$AppClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也成它为系统类加载器。它负责加载用户类路径上指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
  AppClassLoader的parent是ExtClassLoader。很多文章在介绍ClassLoader层次的结构时把Bootstrap ClassLoader也列在ExtClassLoader的上一级中,其实Bootstrap ClassLoader并不属于JVM的类等级层次,因为Bootstrap ClassLoader没有遵守ClassLoader的加载股则。另外Bootstrap ClassLoader没有子类,ExtClassLoader的父类也不是Bootstrap ClassLoader,ExtClassLoader并没有父类,我们在应用中能提取到的顶层父类是ExtClassLoader.
代码举例:

        ClassLoader cl = Thread.currentThread().getContextClassLoader();
System.out.println(cl.toString());
System.out.println(cl.getParent());
System.out.println(cl.getParent().getParent());

  输出结果:

sun.misc.Launcher$AppClassLoader@7dd74c03
sun.misc.Launcher$ExtClassLoader@41bf9980
null

如何获得ClassLoader

  1. this.getClass().getClassLoader();//使用当前类的ClassLoader
  2. Thread.currentThread().getContextClassLoader();//使用当前线程的ClassLoader
  3. ClassLoader.getSystemClassLoader();//使用系统ClassLoader

  代码举例:

    public void test()
{
System.out.println(this.getClass().getClassLoader().toString());
System.out.println(Thread.currentThread().getContextClassLoader());
System.out.println(ClassLoader.getSystemClassLoader());
}

  输出:

sun.misc.Launcher$AppClassLoader@41bf9980
sun.misc.Launcher$AppClassLoader@41bf9980
sun.misc.Launcher$AppClassLoader@41bf9980

[Tips]如何获得类的class属性:
1. Class.forName(类路径全名);
2. this.getClass();
3. 使用.class,比如String.class
4. 对于基本数据类型有:Class c1 = int.class(class只是约定标记,不是成员属性) 或者Class c2 = Integer.TYPE

  JVM加载class文件到内存有两种方式:
  1. 隐式加载:所谓的隐式加载就是不通过在代码里调用ClassLoader来记载需要的类,而是通过JVM来自动加载需要的类到内存的方式。例如,当我们在类中集成或者引用某个类是,JVM在解析当前这个类时发现引用的类不在内存中,那么就会自动将这些类加载到内存中。
  2. 显示加载:相反的显示加载就是我们在代码中通过调用ClassLoader类来加载一个类的方式,例如,调用this.getClass.getClassLoader().loadClass()或者Class.forName(),或者我们自己实现的ClassLoader的findClass()方法等。


双亲委派模型

  双亲委派模型要求畜类鼎城的启动类加载器(Bootstrap ClassLoader)之外,其余的类加载器都应当有自己的父类加载器。这里类加载器之间的父子关系一般不会以inheritance的关系实现而是都是使用组合Composition关系来复用父类加载器的代码
  双亲委派模型不是一个强制性的约束模型,而是java设计者推荐给开发者的一种类加载器实现方式。在java的世界中大部分的类加载器都遵循这个模型,但也有例外,到目前为止,双亲委派模型主要出现过3此较大的“被破坏”情况,这个稍后再阐述。
  双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
  双亲委托机制的作用是防止系统jar包被本地替换,因为查找方法过程都是从最底层开始查找。 因此,一般我们自定义的classloader都需要采用这种机制,我们只需要继承java.lang.ClassLoader实现findclass即可,如果需要更多控制,自定义的classloader就需要重写loadClass方法了,比如tomcat的加载过程,这个比较复杂,可以通过其他文档资料查看相关介绍。
  双亲委派模型对于保证java程序的稳定运作很重要,实现体现在java.lang.ClassLoader的loadClass()方法中,如下:

protected 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;
}
}

  loadClass()方法的加载步骤为:
  1. 调用 Class c = findLoadedClass(name);来检查是否已经加载类;
  2. 在父类加载器上调用loadClass方法:
   c = parent.loadClass(name, false);
  如果父类加载器为null,则使用虚拟机的内置类加载器:
   c = findBootstrapClassOrNull(name);
  3. 在父类加载器无法加载的时候再调用本身的findClass方法来进行类加载:
   c = findClass(name);

每个ClassLoader加载Class的过程是:
1.检测此Class是否载入过(即在cache中是否有此Class),如果有到8,如果没有到2;
2.如果parent classloader不存在(没有parent,那parent一定是bootstrap classloader了),到4;
3.请求parent classloader载入,如果成功到8,不成功到5;
4.请求jvm从bootstrap classloader中载入,如果成功到8;
5.寻找Class文件(从与此classloader相关的类路径中寻找)。如果找不到则到7;
6.从文件中载入Class,到8;
7.抛出ClassNotFoundException;
8.返回Class.


破坏双亲委派模型

  上文提到了到目前为止,双亲委派模型出现过3次较大规模的“被破坏”的情况,这里详细阐述一下。
  第一次。发生在双亲委派模型出现之前(jdk1.2发布之前)。由于双亲委派模型在jdk1.2之后才被引入,而类加载器和抽象类java.lang.ClassLoader则在jdk1.0时代就已经存在,面对已经存在的用户自定义类加载器的实现代码,java设计者引入双亲委派模型时不得不做出了一些妥协。历史已经成为过去,具体的在此不赘述,需要注意的是jdk1.2之后不提倡用户再去覆盖loadClass()方法,而应当把自己的类加载逻辑写到findClass()中,这样保证符合双亲委派模型的规则。
  第二次。由模型本身的缺陷所导致的,双亲委派模型很好地解决了各个类加载器的基础类的统一问题。当父类加载器需要请求子类加载器去完成类加载动作,比如JNDI服务:它的代码由启动类加载器去加载,但JNDI的目的是对资源进行集中管理和查找,它需要调用由独立厂商实现并部署在应用程序的Classpath下的JNDI提供者(SPI)的代码,但是启动类加载器不“认识”这些代码。这是就要用到了线程上下文加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置。
  这里怎么又出来一个context classloader呢?它有什么用呢?我们在建立一个线程Thread的时候,可以为这个线程通过setContextClassLoader方法来指定一个合适的classloader作为这个线程的context classloader,当此线程运行的时候,我们可以通过getContextClassLoader方法来获得此context classloader,就可以用它来载入我们所需要的Class。默认的是system classloader。利用这个特性,我们可以“打破”classloader委托机制了,父classloader可以获得当前线程的context classloader,而这个context classloader可以是它的子classloader或者其他的classloader,那么父classloader就可以从其获得所需的 Class,这就打破了只能向父classloader请求的限制了。这个机制可以满足当我们的classpath是在运行时才确定,并由定制的 classloader加载的时候,由system classloader(即在jvm classpath中)加载的class可以通过context classloader获得定制的classloader并加载入特定的class(通常是抽象类和接口,定制的classloader中是其实现),例如web应用中的servlet就是用这种机制加载的.
  第三次。 由于用户对程序动态性的追求而导致的,这里所说的“动态性”指的是当前一些非常“热门”的名称:代码热替换、模块热部署等,类似于鼠标键盘热拔插。具体的可以看一下OSGi,在OSGi环境下,类加载器不再是双亲委派模型中的树型结构,而是一种网状结构。具体的可以翻阅一些相关资料。


推荐阅读
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 纠正网上的错误:自定义一个类叫java.lang.System/String的方法
    本文纠正了网上关于自定义一个类叫java.lang.System/String的错误答案,并详细解释了为什么这种方法是错误的。作者指出,虽然双亲委托机制确实可以阻止自定义的System类被加载,但通过自定义一个特殊的类加载器,可以绕过双亲委托机制,达到自定义System类的目的。作者呼吁读者对网上的内容持怀疑态度,并带着问题来阅读文章。 ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • 本文介绍了在Android开发中使用软引用和弱引用的应用。如果一个对象只具有软引用,那么只有在内存不够的情况下才会被回收,可以用来实现内存敏感的高速缓存;而如果一个对象只具有弱引用,不管内存是否足够,都会被垃圾回收器回收。软引用和弱引用还可以与引用队列联合使用,当被引用的对象被回收时,会将引用加入到关联的引用队列中。软引用和弱引用的根本区别在于生命周期的长短,弱引用的对象可能随时被回收,而软引用的对象只有在内存不够时才会被回收。 ... [详细]
  • 开发笔记:spring boot项目打成war包部署到服务器的步骤与注意事项
    本文介绍了将spring boot项目打成war包并部署到服务器的步骤与注意事项。通过本文的学习,读者可以了解到如何将spring boot项目打包成war包,并成功地部署到服务器上。 ... [详细]
  • 本文介绍了在sqoop1.4.*版本中,如何实现自定义分隔符的方法及步骤。通过修改sqoop生成的java文件,并重新编译,可以满足实际开发中对分隔符的需求。具体步骤包括修改java文件中的一行代码,重新编译所需的hadoop包等。详细步骤和编译方法在本文中都有详细说明。 ... [详细]
  • Spring框架《一》简介
    Spring框架《一》1.Spring概述1.1简介1.2Spring模板二、IOC容器和Bean1.IOC和DI简介2.三种通过类型获取bean3.给bean的属性赋值3.1依赖 ... [详细]
  • OpenMap教程4 – 图层概述
    本文介绍了OpenMap教程4中关于地图图层的内容,包括将ShapeLayer添加到MapBean中的方法,OpenMap支持的图层类型以及使用BufferedLayer创建图像的MapBean。此外,还介绍了Layer背景标志的作用和OMGraphicHandlerLayer的基础层类。 ... [详细]
  • 本文介绍了解决java开源项目apache commons email简单使用报错的方法,包括使用正确的JAR包和正确的代码配置,以及相关参数的设置。详细介绍了如何使用apache commons email发送邮件。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
author-avatar
拍友2702932701
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有