由来:
与普通程序不同的是,Java程序(class文件)并非本地的可执行程序(解释性语言)。
当执行Java程序时。首先执行JVM(Java虚拟机),然后再把Javaclass载入到JVM里头执行,负责载入Javaclass的这部分就ClassLoader。
中文叫做类载入器。
类载入器就好比一个代理,你须要什么。我通过类载入器将你须要的内容返回给你!
类载入器有什么作用?
当程序须要的某个类,那么须要通过类载入器把类的二进制载入到内存中.
解释:
类载入器也是Java类,由于其它是java类的类载入器本身也要被类载入器载入,显然必须有第一个类载入器不是java类,这正是BootStrap。
Java虚拟机中的全部类装载器採用具有父子关系的树形结构进行组织。在实例化每一个类装载器对象时,须要为其指定一个父级类装载器对象或者默认採用系统类装载器为其父级类载入。
Java类载入器:
Java中的类载入器大致能够分成两类。一类是系统提供的。另外一类则是由 Java 应用开发者编写的。
系统提供的类载入器主要有以下三个:
引导类载入器(bootstrap classloader):
它用来载入 Java 的核心库,是用原生代码来实现的,并不继承自java.lang.ClassLoader。
扩展类载入器(extensions classloader):
它用来载入 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库文件夹。该类载入器在此文件夹里面查找并载入 Java 类。
系统类载入器(system classloader):
它依据 Java 应用的类路径(CLASSPATH)来载入 Java 类。一般来说。Java 应用的类都是由它来完毕载入的。能够过ClassLoader.getSystemClassLoader() 来获取它。
关系:
能够简单理解它们三者之间是父与子的关系。不同的是每一个ClassLoader本身仅仅能分别载入特定位置和文件夹中的类,但它们能够托付其它的类装载器去载入类。这也就是类载入器的托付模式。托付的过程是子类托付父类!
除了系统提供的类载入器以外,开发者能够通过继承java.lang.ClassLoader 类的方式实现自己的类载入器,以满足一些特殊的需求。
类载入器的托付机制(双亲委派机制):
1>当Java虚拟机要载入一个类时,究竟派出哪个类载入器去载入呢?
①首先当前线程的类载入器去载入线程中的第一个类.
②假设类A中引用了类B,Java虚拟机将使用载入类A的类载入器载入类B
③还能够直接调用ClassLoader.loadClass()方法来指定某个类载入器去载入某个类.
2>每一个类载入器载入类时,又先托付给其上级类载入器.
①类装载器一级级托付到BootStrap类载入器,当BootStrap无法载入当前所要载入的类时,然后才一级级回退到子孙类装载器去进行真正的载入。当回退到最初的类装载器时,假设它自己也不能完毕类的装载,那就应报告ClassNotFoundException异常。
简单解释双亲委派机制:
为了安全。java中有String或者其它的核心类,我们想覆盖是覆盖不了的,由于我们自己写的类都属于在classpath路径下。那classpath的装载器都是系统类装载器,即使我们写了一个一模一样的String,它也会一直请求,一直请求到根,假设上面有一个能载入,比如根,那么它就用根载入那个对象给我们返回,我们自己的永远载入不上。这种话我们不至于破坏java的核心库
代码演示样例:
public class Test {
public static void main(String[] args) {
//载入器:sun.misc.Launcher$AppClassLoader
System.out.println(Test.class.getClassLoader().getClass().getName());
//载入器:BootStrap(loader为null的情况)
System.out.println(System.class.getClassLoader());//
System.out.println("----------------查看类载入器的层次结构关系-------------------");
ClassLoader loader = Test.class.getClassLoader();
while(loader != null){
System.out.println(loader.getClass().getName());
loader = loader.getParent();
}
System.out.println(loader);
}
/**
* 执行结果:
* sun.misc.Launcher$AppClassLoader
null
----------------查看类载入器的层次结构关系-------------------
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
null
*/
}
能不能自己写一个类叫java.lang.System?
为了不让我们写System类,类载入採用托付机制,这样能够保证爸爸优先,也就是使用的永远是爸爸的(系统的)System类,而不是我们写的System类.
设计动机:
Java 虚拟机是怎样判定两个 Java类是同样的呢?
Java虚拟机不仅要看类的全名是否同样,还要看载入此类的类载入器是否一样。
仅仅有两者都同样的情况,才觉得两个类是同样的。即便是同样的字节代码,被不同的类载入器载入之后所得到的类。也是不同的。
比方一个Java 类com.example.Sample。编译之后生成了字节代码文件 Sample.class。两个不同的类载入器 ClassLoaderA 和ClassLoaderB 分别读取了这个 Sample.class 文件,并定义出两个 java.lang.Class 类的实例来表示这个类。这两个实例是不同样的。
对于Java 虚拟机来说,它们是不同的类。
了解了以上这一点之后,就能够理解设计动机了,这样的托付机制是为了保证Java 核心库的类型全。
全部 Java 应用都至少须要引用 java.lang.Object 类。也就是说在执行的时候,java.lang.Object这个类须要被载入到 Java 虚拟机中。假设这个载入过程由 Java 应用自己的类载入器来完毕的话。非常可能就存在多个版本号的java.lang.Object类,并且这些类之间是不兼容的。通过托付机制,对于 Java 核心库的类的载入工作由引导类载入器来统一完毕。保证了 Java 应用所使用的都是同一个版本号的Java 核心库的类。是互相兼容的。
类载入器与 Web 容器:
对于执行在容器中的 Web 应用来说。类载入器的实现方式与一般的 Java 应用有所不同。不同的 Web 容器的实现方式也会有所不同。
以Apache Tomcat 来说,每一个 Web 应用都有一个相应的类载入器实例。该类载入器也使用托付机制,所不同的是它是首先尝试去载入某个类。假设找不到再托付给父类载入器。这与一般类载入器的顺序是相反的。这是 Java Servlet 规范中的推荐做法。其目的是使得 Web应用自己的类的优先级高于 Web 容器提供的类。
这样的托付的一个例外是:Java 核心库的类是不在查找范围之内的。
这也是为了保证 Java核心库的类型安全。
Tomcat载入器:
和第一幅图有什么关系呢?
本图是基于第一幅图(java自带机制)的又一层封装,它是基于第一幅图的,仅仅是tomcat在java的基础上自己定义了一些非核心类库的载入器,以供用户使用!
总结:
绝大多数情况下。对于Web应用的开发者不须要考虑与类载入器相关的细节。可是当出现ClassNotFoundException异常我们能大概了解问题所在。
以下给出几条简单的原则:
每一个 Web 应用自己的 Java类文件和使用的库的 jar 包,分别放在 WEB-INF/classes和 WEB-INF/lib文件夹以下。
多个应用共享的 Java 类文件和 jar包。分别放在 Web 容器指定的由全部 Web 应用共享的文件夹以下。
当出现找不到类的错误时,检查当前类的类载入器和当前线程的上下文类载入器是否正确。这些原则也是依据java类载入器和自己定义载入器的实现方式总结而来。须要我们简单的理解!
版权声明:本文博客原创文章,博客,未经同意,不得转载。