[读书笔记]Java类加载器

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[读书笔记]Java类加载器相关的知识,希望对你有一定的参考价值。

一、类与类加载器

类加载器除了在类加载阶段的作用外,还确定了对于一个类,都需要由加载它的类加载器和这个类本身一同确定其在Java虚拟机中的唯一性。通俗一点来讲,要判断两个类是否“相等”,前提是这两个类必须被同一个类加载器加载,否则这个两个类不“相等”。
这里指的“相等”,包括类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法、instanceof关键字等判断出来的结果。

示例:不同的类加载器对instanceof关键字结果的影响

  1. package org.kesar;
  2. import java.io.IOException;
  3. import java.io.InputStream;
  4. public class ClassLoaderTest
  5. {
  6. /**
  7. * @param args
  8. * @throws Exception
  9. */
  10. public static void main(String[] args) throws Exception
  11. {
  12. ClassLoader myLoader = new ClassLoader()
  13. {
  14. @Override
  15. public Class<?> loadClass(String name)
  16. throws ClassNotFoundException
  17. {
  18. try
  19. {
  20. String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
  21. InputStream is = getClass().getResourceAsStream(fileName);
  22. if (is == null)
  23. {
  24. return super.loadClass(name);
  25. }
  26. byte[] b = new byte[is.available()];
  27. is.read(b);
  28. return defineClass(name, b, 0, b.length);
  29. }
  30. catch (IOException e)
  31. {
  32. throw new ClassNotFoundException(name);
  33. }
  34. }
  35. };
  36. Object obj=myLoader.loadClass("org.kesar.ClassLoaderTest").newInstance();
  37. System.out.println(obj.getClass());
  38. System.out.println(obj instanceof ClassLoaderTest);
  39. }
  40. }

输出结果:
class org.kesar.ClassLoaderTest
false

结果分析:
由于ClassLoaderTest在虚拟机中存在两个,一个是由系统应用程序类加载器加载,另一个是由我们自定义的类加载器加载的,所以两个类并不“相等”。

二、双亲委派模型

从Java虚拟机来讲的话,目前的类加载器有不同的两种。一种是启动类加载器(Bootstrap ClassLoader),是虚拟机中的一部分;另一种是其他的所有类加载器,独立于虚拟机外部的,继承自抽象类java.lang.ClassLoader
从系统提供的类加载器来讲,有这3种类加载器:启动类加载器、扩展类加载器、和应用程序类加载器。

1. 系统的类加载器

(1)启动类加载器(Bootstrap ClassLoader)
加载内容:<JAVA_HOME>\lib目录、被-Xbootclasspath参数所指定的路径中的虚拟机可识别的类库加载到虚拟机内存中。
特点:不能被Java程序直接引用,如果想将加载委托请求给启动类加载器加载,直接使用null代替即可。

示例:java.lang.Class的getClassLoader()源码

  1. /**
  2. * Returns the class loader for the class. Some implementations may use
  3. * null to represent the bootstrap class loader. This method will return
  4. * null in such implementations if this class was loaded by the bootstrap
  5. * class loader.
  6. */
  7. public ClassLoader getClassLoader() {
  8. ClassLoader cl = getClassLoader0();
  9. if (cl == null)
  10. return null;
  11. SecurityManager sm = System.getSecurityManager();
  12. if (sm != null) {
  13. ClassLoader.checkClassLoaderPermission(cl, Reflection.getCallerClass());
  14. }
  15. return cl;
  16. }

(2)扩展类加载器(Extension ClassLoader)
加载内容:<JAVA_HOME>\lib\ext目录、被java.ext.dirs系统变量所指定的路径中的所有类库
特点:使用Java编写,开发者可以直接使用扩展类加载器

(3)应用程序类加载器(Application ClassLoader)
加载内容:加载用户类路径(ClassPath)上所指定的类库
特点:开发者可以直接使用这个类加载器,如果没有自定义的类加载器,将默认使用它。

2. 双亲委派模型(Parents Delegation Model)

工作过程:当类加载器受到类加载请求时,一般不会自己去加载这个类,将会将这个类加载请求委派父类加载器去完成,每一层次的类加载器都是这么做的,只有当这个类没有父类加载器时(Bootstrap ClassLoader)或父类加载器反馈无法加载这个类时(搜索范围中没有找到所需的类),子加载器才会自己去加载。

类的双亲委派模型如下图:
技术分享

好处:使用双亲委派模型,由于其工作过程的特点,Java类随着它的类加载器一起具备了一种带有优先级的层次关系(层次越高越优先加载类)。比如:java.lang.Object,每个类都默认继承自这个类,故每次类加载都会加载这个类,而该类存在rt.jar中,而使用双亲委派模型,每次类价值请求都将会委派到启动类加载器,故将可以成功加载到java.lang.Object。而且java.lang.Object每次的委派都是被启动类加载器加载,这样保证了类在虚拟机中的一致性。如果失去这种机制,每个类加载器将自己加载一个java.lang.Object,那么系统中将会有多个不相等的Object类,将会导致应用程序一片混乱。

可以看看ClassLoader的loadClass()方法源码:

  1. protected Class<?> loadClass(String name, boolean resolve)
  2. throws ClassNotFoundException
  3. {
  4. synchronized (getClassLoadingLock(name)) {
  5. // 首先,检查请求的类是否已经被加载过
  6. Class c = findLoadedClass(name);
  7. if (c == null) {
  8. long t0 = System.nanoTime();
  9. try {
  10. if (parent != null) {
  11. c = parent.loadClass(name, false);
  12. } else {
  13. c = findBootstrapClassOrNull(name);
  14. }
  15. } catch (ClassNotFoundException e) {
  16. // 如果父类抛出 ClassNotFoundException
  17. // 说明父类加载器无法完成加载请求
  18. }
  19. if (c == null) {
  20. // 如果父类加载器找不到,则自己进行类加载
  21. long t1 = System.nanoTime();
  22. c = findClass(name);
  23. // this is the defining class loader; record the stats
  24. sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
  25. sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
  26. sun.misc.PerfCounter.getFindClasses().increment();
  27. }
  28. }
  29. if (resolve) {
  30. resolveClass(c);
  31. }
  32. return c;
  33. }
  34. }

3. 三次破坏双亲委派模型

(1)第一破坏:JDK版本问题
问题:JDK1.2之前是没有双亲委派模型的。JDK1.0时ClassLoader就已经存在,这时就可以自定义类加载器了,是要重写ClassLoader的findClass()方法来实现的。但是在引入双亲委派模型后,重写findClass()一不小心就会破坏双亲委派模型。
处理:JDK1.2后,为ClassLoader增加一个可重写的loadClass()方法,规定以后自定义类加载器就重写loadClass()就可以,避免重写findClass()破坏双亲委派模型。

(2)第二破坏:模型自身缺陷问题
问题:双亲委派模型无法从父类加载器去请求子类加载器委派加载类。比如有一个典型的例子JNDI服务,JNDI现在已经是Java的标准服务,它的代码是由启动类加载器去加载的,需要调用应用程序在ClassPath下的JNDI接口提供者的代码,但启动类加载器不“认识”这些代码。
处理:使用了线程上下文类加载器(Thread Context ClassLoader),使得父类加载器可以请求子类加载器去完成类加载的动作,达到了逆向使用类加载器的效果,打通双亲委托模型的层次结构。

(3)第三破坏:“动态性”问题
问题:程序动态性的发展:代码热替换、模块热部署等。
处理:定制了Java模块化标准OSGI,在这种环境下,将不会是双亲委派模型的树状结构了,而是进一步发展为网结构。

附:OSGI顺序执行过程
1) 将java.*开头的类委托给父类加载器加载。
2) 否则,将委托列表名单内的类委派给父类加载器加载。
3) 否则,将Import列表中的类委托给Export这个类的Bundle的类加载器加载。
4) 否则,查找当前Bundle的ClassPath,使用自己的类加载器加载。
5) 否则,查找类是否在自己的Fragment Bundle中,如果在,则委派给Fragment Bundle的类加载器加载。
6) 否则,查找Dynamic Import列表的Bundle,委派给对应Bundle的类加载器加载。
7) 否则,类查找失败。
































以上是关于[读书笔记]Java类加载器的主要内容,如果未能解决你的问题,请参考以下文章

深入理解JAVA虚拟机读书笔记——虚拟机类加载过程和双亲委派模型

深入理解JVM读书笔记三: 虚拟机类加载机制

读书笔记

TJI读书笔记10-复用类

《深入理解计算机系统》 Chapter 7 读书笔记

读书笔记