由来:
与普通程序不同的是,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类加载器和自定义加载器的实现方式总结而来,需要我们简单的理解!