对象建立过程之二(类加载器)

JAVA为咱们提供了两种动态加载机制。
第一种是隐式机制。其实new一个对象和调用类的静态方法时,就是隐式机制在工做。
第二种是显示机制。显示的机制又有两种策略
第一种是用public static Class<?> forName(String className)。public static Class<?> forName(String name, boolean initialize, ClassLoader loader),第二种是用java.lang.ClassLoader的loadClass())。 java

Java程序启动时,并非一次把全部的类所有加载后再运行,它老是先把保证程序运行的基础类一次性加载到jvm中,其它类等到jvm用到的时候再加载。
其中类加载过程:
一、寻找 jre 目录,寻找jvm.dll,并初始化JVM;
二、产生一个Bootstrap Loader(启动类加载器,用C++实现),在java虚拟机启动的时候会利用这个类加载器来加载  JDK安装目录下的  /JRE/LIB/rt.jar等文件。  经过System.getProperty("sun.boot.class.path")能够查询。
三、Bootstrap Loader自动加载Extended Loader(标准扩展类加载器 ,用java实现),并将其父Loader设为Bootstrap Loader。这个ClassLoader是用来加载java的扩展API的,加载JDK安装目录下的/JRE/LIB/ext目录中的类。能够经过System.getProperty("java.ext.dirs")进行查询。
也能够指定搜索路径: java -Djava.ext.dirs=d:/projects/testproj/classes HelloWorld
四、Bootstrap Loader自动加载AppClass Loader(系统类加载器 用java实现),并将其父Loader设为Extended Loader。这个ClassLoader是用来加载用户机器上CLASSPATH设置目录中的Class的。经过System.getProperty("java.class.path")能够查询。也能够覆盖环境变量: java -cp ./lavasoft/classes HelloWorld
五、最后由AppClass Loader加载HelloWorld类。
以上就是类加载的最通常的过程。
ExtClassLoader和AppClassLoader在JVM启动后,会在JVM中保存一份,而且在程序运行中没法改变其搜索路径。若是想在运行时从其余搜索路径加载类,就要产生新的类加载器。 web

在JAVA中,一个类用其彻底匹配类名(fully qualified class name)做为标识,这里指的彻底匹配类名是包名和类名。不过在JVM中一个类是用其全名再附加上一个加载类ClassLoader的实例做为惟一标识。 apache

同一个ClassLoader加载的类文件,只有一个Class实例。 
可是,若是同一个类文件被不一样的ClassLoader载入,则会有两份不一样的ClassLoader实例(前提是着两个类加载器不能用相同的父类加载器)
双亲委托模式:在任何一个自定义ClassLoader加载一个类以前,它都会先委托它的父亲ClassLoader进行加载,只有当父亲ClassLoader没法加载成功后,才会由本身加载。
《特例是线程上下文类加载器,使用线程上下文类加载器, 能够在执行线程中, 抛弃双亲委派加载链模式, 使用线程上下文里的类加载器加载类.
典型的例子有, 经过线程上下文来加载第三方库jndi实现, 而不依赖于双亲委派. 大部分java app服务器(jboss, tomcat..)也是采用contextClassLoader来处理web服务。
以 Apache Tomcat 来讲,每一个 Web 应用都有一个对应的类加载器实例。该类加载器也使用代理模式,所不一样的是它是首先尝试去加载某个类,若是找不到再代理给父类加载器。这与通常类加载器的顺 序是相反的。这是 Java Servlet 规范中的推荐作法,其目的是使得 Web 应用本身的类的优先级高于 Web 容器提供的类。
这种代理模式的一个例外是:Java 核心库的类是不在查找范围以内的。这也是为了保证 Java 核心库的类型安全。 》 tomcat

在JVM加载类的时候,须要通过三个步骤,装载、链接、初始化。装载就是找到相应的class文件,读入JVM,初始化就不用说了,最主要就说说链接。
forName加载的时候就会将Class进行解释和初始化。forName也有另一个版本的方法,能够设置是否初始化以及设置ClassLoader,在此就很少讲了。 loadClass加载类实际上就是加载的时候并不对该类进行解释,所以也不会初始化该类。
  安全

加载过程当中会先检查类是否被已加载,检查顺序是自底向上,而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。
卸载重载:一个已经加载的类是没法被更新的,若是你试图用同一个ClassLoader再次加载同一个类,就会获得异常(java.lang.LinkageError: duplicate classdefinition),咱们只可以从新建立一个新的ClassLoader实例来再次加载新类。至于原来已经加载的类,开发人员没必要去管它,由于它可能还有实例正在被使用,只要相关的实例都被内存回收了,那么JVM就会在适当的时候把不会再使用的类卸载。 服务器

真正完成类的加载工做是经过调用 defineClass来实现的;而启动类的加载过程是经过调用 loadClass来实现的。前者称为一个类的定义加载器(defining loader),后者称为初始加载器(initiating loader)。在 Java 虚拟机判断两个类是否相同的时候,使用的是类的定义加载器。 app

《注意事项:若是A是由Bootstrap Loader所载入,这个时候,要加入B,先交给parent进行查询,这时parent为null,交给本身查询,本身又没有,就报错。》 jvm

 

线程上下文类加载器 ide

线程上下文类加载器(context class loader)是从 JDK 1.2 开始引入的。类 java.lang.Thread 中的方法getContextClassLoader()  setContextClassLoader(ClassLoader cl) 用来获取和设置线程的上下文类加载器。若是没有经过 setContextClassLoader(ClassLoader cl) 方法进行设置的话,线程将继承其父线程的上下文类加载器。Java 应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码能够经过此类加载器来加载类和资源。 ui

 前面提到的类加载器的代理模式并不能解决 Java 应用开发中会遇到的类加载器的所有问题。Java 提供了不少服务提供者接口(Service Provider Interface,SPI),容许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。这些 SPI 的接口由 Java 核心库来提供,如 JAXP 的 SPI 接口定义包含在javax.xml.parsers 包中。这些 SPI 的实现代码极可能是做为 Java 应用所依赖的 jar 包被包含进来,能够经过类路径(CLASSPATH)来找到,如实现了 JAXP SPI 的 Apache Xerces 所包含的 jar 包。SPI 接口中的代码常常须要加载具体的实现类。如 JAXP 中的 javax.xml.parsers.DocumentBuilderFactory 类中的newInstance() 方法用来生成一个新的 DocumentBuilderFactory 的实例。这里的实例的真正的类是继承自javax.xml.parsers.DocumentBuilderFactory , 由 SPI 的实现所提供的。如在 Apache Xerces 中,实现的类是 org.apache.xerces.jaxp.DocumentBuilderFactoryImpl 。 而问题在于,SPI 的接口是 Java 核心库的一部分,是由引导类加载器来加载的;SPI 实现的 Java 类通常是由系统类加载器来加载的。引导类加载器是没法找到 SPI 的实现类的,由于它只加载 Java 的核心库。它也不能代理给系统类加载器,由于它是系统类加载器的祖先类加载器。也就是说,类加载器的代理模式没法解决这个问题。

 线程上下文类加载器正好解决了这个问题。若是不作任何的设置,Java 应用的线程的上下文类加载器默认就是系统上下文类加载器。在 SPI 接口的代码中使用线程上下文类加载器,就能够成功的加载到 SPI 实现的类。线程上下文类加载器在不少 SPI 的实现中都会用到.

相关文章
相关标签/搜索