前言

虚拟机类加载阶段“通过一个类的全限定名来获取此类的二进制字节流”这个动作是Java虚拟机之外实现的,所以程序可以自定义如何去获取所需要的加载类。

类加载器

对于任意一个类来说,此类的类加载器和类本身共同确定了类的唯一性。即,一个类的Class文件相同,类加载器相同,才能是同一个类。

类加载

Java 虚拟机角度,只有2种类加载器:启动类加载器(Bootstrap ClassLoader);其他类加载器。
对于Java开发来说,可以再次细致划分下,分为3种:

  • 启动类加载器(Bootstrap ClassLoader):加载JAVAHOME/lib目录中的class,或者指定。
  • 扩展类加载器(Extension ClassLoader):加载JAVAHOME/lib/ext目录中的class,或者指定。
  • 应用程序类加载器(Application ClassLoader):加载用户类路径上(ClassPath)的类库。

    注:应用类加载器可以直接使用,如果没有定义过类加载器,这就是默认的类加载器。

双亲委派模型

要求:除了顶层的启动类加载器歪,其他的类加载都要有自己的父类加载器。
工作过程:类加载器收到类加载请求时,先将请求委派给父类去加载,每一层都是如此,一直传递到顶层的启动类加载器,父类加载好就直接返回,无法加载时(自己加载的类范围内找不到这个Class),子加载器才会尝试加载。

源码分析

源码都在ClassLoader当中:

  1. //系统类加载器,即应用程序加载类 Application ClassLoader
  2. public static ClassLoader getSystemClassLoader() {
  3. initSystemClassLoader();
  4. if (scl == null) {
  5. return null;
  6. }
  7. SecurityManager sm = System.getSecurityManager();
  8. if (sm != null) {
  9. checkClassLoaderPermission(scl, Reflection.getCallerClass());
  10. }
  11. return scl;
  12. }
  13. protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
  14. synchronized (getClassLoadingLock(name)) {
  15. // First, check if the class has already been loaded
  16. //检查类是否已经加载过了
  17. Class<?> c = findLoadedClass(name);
  18. if (c == null) {
  19. long t0 = System.nanoTime();
  20. try {
  21. if (parent != null) {
  22. //父加载器去加载
  23. c = parent.loadClass(name, false);
  24. } else {
  25. c = findBootstrapClassOrNull(name);
  26. }
  27. } catch (ClassNotFoundException e) {
  28. // ClassNotFoundException thrown if class not found
  29. // from the non-null parent class loader
  30. }
  31. if (c == null) {
  32. // If still not found, then invoke findClass in order
  33. // to find the class.
  34. long t1 = System.nanoTime();
  35. //父加载器无法加载的情况下,调用本身的findClass方法加载
  36. c = findClass(name);
  37. // this is the defining class loader; record the stats
  38. sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
  39. sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
  40. sun.misc.PerfCounter.getFindClasses().increment();
  41. }
  42. }
  43. if (resolve) {
  44. resolveClass(c);
  45. }
  46. return c;
  47. }
  48. }
  49. //实现自己类加载
  50. protected Class<?> findClass(String name) throws ClassNotFoundException {
  51. throw new ClassNotFoundException(name);
  52. }

如果有自己的类加载器,那么实现findClass()方法。

破坏双亲委派模型

大部分情况的类加载器都遵循这个模型,也有特殊情况。

  1. 在Java 1.2之前未发明双亲委派时,发生的破坏。
  2. 当基础类加载用户代码时,无法实现,如:JNDI、JDBC等。此时,采取线程上下文类加载器(Thread Context ClassLoader),自己设置类加载器,进行类的加载。
  3. 程序动态性,导致需要热加载等等。OSGi类加载机制,开发出来网状的类加载机制。

Tomcat类加载器架构

Web服务器需要解决问题:

  1. 一个服务器上多个Web使用的Java类库互相隔离。解决同时依赖不同的第三方版本时,必须都加载。
  2. 一个服务器上多个Web共享使用的Java类库。节约资源。
  3. 服务器的类加载不受到Web应用程序的影响。
  4. 支持JSP应用的Web服务器,需要热部署机制。

Tomcat为此规划了很多类加载器,目录也进行了区分。

  • common目录:tomcat和web都可以加载访问。
  • server目录:仅仅tomcat加载访问。
  • shared目录:仅仅web可以加载访问。
  • /WebApp/WEB-INF目录:仅web应用程序加载访问。

应用程序类加载器 <— Common类加载器 <— Catalina类加载器
应用程序类加载器 <— Common类加载器 <— Shared类加载器 <— WebApp类加载器 <— Jsp类加载器

技巧

当发生Jar冲突时,即可采取类加载器区分加载。

参考