Java类加载器(Java Classloader)是Java运行时环境(Java Runtime Environment)的一部分,负责动态加载Java类到Java虚拟机的内存空间中。
类加载器:
对于HotSpot虚拟机而言,类加载器大体分为两种:属于JVM本身的类加载器,如:Bootstrap ClassLoader;不属于JVM本身的类加载器,如:Extensions ClassLoader、Application ClassLoader、用户自定义类加载器:
◎Bootstrap ClassLoader(引导类加载器):
HotSpot虚拟机(由C、C++等语言编写)本身包含了一个名为Bootstrap ClassLoader的类加载器;BootstrapClassLoader是用C++编写的,它负责将核心JavaClass加载到虚拟机内存中。
注:核心JavaClass指:
1、<JAVA_HOME>/jre/lib下的且被虚拟机(按名称)识别的类库,如:rt.jar。
2、被-Xbootclasspath参数指定的路径中的 且被拟机识别的类库。
注:在编写自定义的加载器时,并不能被显示的指定parent为Bootstrap ClassLoader,如果想把parent设置为引导类
加载器,那么直接使parent值为null即可;或者直接就想用引导类加载器的话,直接使该加载器为null即可。
◎ExtClassLoader(即:扩展类加载器):
此加载器本身由BootstrapClassLoader加载;负责加载扩展的Javaclass。该类处于sun.misc.Launcher$ExtClassLoader。
注:扩展JavaClass指:
1、<JAVA_HOME>/jre/ext下的所有类库。
2、系统变量java.ext.dirs指定的目录下的所有类库。
◎AppClassLoader(也称:App类加载器、系统类加载器):
此加载器本身由BootstrapClassLoader加载;负责加载Java应用程序类路径(classpath路径)下的类库。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。该类处于sun.misc.Launcher$AppClassLoader。
最基本的加载流程:
当运行一个程序的时候,JVM启动,运行BootstrapClassLoader,该ClassLoader加载java核心API(ExtClassLoader和AppClassLoader也在此时被加载),然后调用ExtClassLoader加载扩展API,最后AppClassLoader加载CLASSPATH目录下定义的Class,这就是一个程序最基本的加载流程。
类加载的双亲委派模式:
测试ClassLoader的parent属性:
注:类加载器之间的类关系是关联而不是泛化(详细点说:是组合而不是继承)。AppClassLoader的parent是
ExtClassLoader,ExtClassLoader的parent是null(注:当一个ClassLoader的parent为null时,就说明这个
类加载器的parent是Bootstrap ClassLoader)。
双亲委派模式里,一个类的加载流程:
假设现在用户自定义了一个类加载器:
注:自定义类加载器,需要继承ClassLoader,并重写findClass方法。
追注:JDK1.2之后就不推荐重写loadClass方法了,而是推荐重写findClass方法,在
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
方法里会调用protected Class<?> findClass(String name) throws ClassNotFoundException方法,
详见源码。
那么加载一个类的流程为:
注:双亲委派模式很好的解决了各个加载器对基础类的加载的问题(越基础的类,越上层加载),保证了基础类库的安
全性。
类加载器的再演变(双亲委派模型的破坏):
双亲委派模式只是类加载器设计者推荐的一种模式,并不是一定要遵循的。在某些场景里,可能或用到直接加载某些类。
如JNDI(即:Java Naming and Directory Interface),JNDI是SUN公司提供的一种标准的Java命名系统接口,JNDI提供统一的客户端API,由不同的独立厂商来对这些接口进行各自的实现,由管理者将JNDI API映射为特定的命名服务和目录系统,使得Java应用程序可以和这些命名服务和目录服务之间进行交互。
JNDI位于rt.jar下,由Bootstrap ClassLoader加载,JNDI的目的是对资源进行集中管理和查找,JNDI需要调用由第三方独立厂商提供并部署在应用程序的classpath下SPI(Service Provider Interface)实现类。因为Bootstrap ClassLoader并不能加载这些类,为了解决这个问题,Java设计团队就引入了Thread Context ClassLoader线程上下文类加载器。这样一来Bootstrap ClassLoader加载JNDI后,JNDI再通过Thread Context ClassLoader去加载对应的类到虚拟机内存(注:这样其实就打破了双亲委托原则了)。
注:Thread Context ClassLoader可以通过 java.lang.Thread类的setContextClassLoaser()方法进行设置;如果
创建线程时还未设置,它将会从父线程中继承个,如果在应用程序的全局范围内都没有设置过的话,那这个类加
载器默认就是AppClassLoader。
随着用户对程序动态性(如:代码热替换、模块儿热部署)的追求,类加载又有进一步的演变。OSGI(Open Service Gateway Initiative)技术是Java动态化模块化系统的一系列规范。在OSGI环境下,类加载器不再是双亲委派模型中的树状结构,而是进一步发展为更加复杂的网状结构,当收到类加载请求时,OSG将按照下面的顺序进行类搜索:
- 将以java.*开头的类委派给父类加载器加载。
- 否则,将委派列表名单内的类委派给父类加载器加载。
- 否则,将 Import列表中的类委派给 Export这个类的 Bundle的类加载器加载。
- 否则,查找当前 Bundle的 Class path,使用自己的类加载器加载。
- 否则,查找类是否在自己的 Fragment Bundle中,如果在,则委派给 Fragment Bundle的类加载器加载。
- 否则,查找 Dynamic Import列表的 Bundle,委派给对应 Bundle的类加载器加载。
- 否则,类查找失败。
注上述步骤中1、2步走的还是双亲委派模式。
声明:本人部分内容直接摘录自《深入理解Java虚拟机》 周志鹏著。