【语言学习】JAVA ClassLoader - hippowc/hippowc.github.io GitHub Wiki

基础

Class文件

class文件是字节码格式文件,java虚拟机并不能直接识别我们平常编写的.java源文件,所以需要javac这个命令转换成.class文件。另外,如果用C或者PYTHON编写的程序正确转换成.class文件后,java虚拟机也是可以识别运行的。

关于ClassLoader

ClassLoader用来加载class文件的。

  • Bootstrap ClassLoader 最顶层的加载类,主要加载核心类库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。另外需要注意的是可以通过启动jvm时指定-Xbootclasspath和路径来改变Bootstrap ClassLoader的加载目录。
  • Extention ClassLoader 扩展的类加载器,加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。还可以加载-D java.ext.dirs选项指定的目录。
  • Appclass Loader也称为SystemAppClass 加载当前应用的classpath的所有类

sun.misc.Launcher,它是一个java虚拟机的入口应用。Launcher初始化了ExtClassLoader和AppClassLoader。Launcher中并没有看见BootstrapClassLoader,但通过System.getProperty("sun.boot.class.path")得到了字符串bootClassPath,这个应该就是BootstrapClassLoader加载的jar包路径。

// 可以通过以下代码查看,BootstrapClassLoader的类加载路径
System.out.println(System.getProperty("sun.boot.class.path"));
// 这样可以获得并查看当前类的ClassLoader是谁
ClassLoader cl = Test.class.getClassLoader();
System.out.println("ClassLoader is:"+cl.toString());

每个类加载器都有一个父加载器

// 通过getParent方法,获得父加载器
System.out.println("ClassLoader\'s parent is:"+cl.getParent().toString());

但是我们调用ExtClassLoader的getParent方法是会报空指针的错误,是它没有父加载器吗?

父加载器不是父类

static class ExtClassLoader extends URLClassLoader {}
static class AppClassLoader extends URLClassLoader {}

他们都继承自 URLClassLoader

而父加载器是在创建ClassLoader时设置的,如果一个ClassLoader创建时没有指定parent,那么它的parent默认就是AppClassLoader。AppClassLoader的父加载器是ExtClassLoader。ExtClassLoader的父加载器为null,Bootstrap ClassLoader是由C/C++编写的,它本身是虚拟机的一部分,所以它并不是一个JAVA类

JVM启动时通过Bootstrap类加载器加载rt.jar等核心jar包中的class文件,之前的int.class,String.class都是由它加载。所以直接使用int.class.getClassLoader() 是null

双亲委派

一个类加载器查找class和resource时,是通过“委托模式”进行的,它首先判断这个class是不是已经加载成功,如果没有的话它并不是自己进行查找,而是先通过父加载器,然后递归下去,直到Bootstrap ClassLoader,如果Bootstrap classloader找到了,直接返回,如果没有找到,则一级一级返回,最后到达自身去查找这些对象。这种机制就叫做双亲委托。

委托是从下向上,然后具体查找过程却是自上至下。

ClassLoader 方法介绍

loadClass()

  • 执行findLoadedClass(String)去检测这个class是不是已经加载过了。
  • 执行父加载器的loadClass方法。如果父加载器为null,则jvm内置的加载器去替代,也就是Bootstrap ClassLoader。这也解释了ExtClassLoader的parent为null,但仍然说Bootstrap ClassLoader是它的父加载器。
  • 如果向上委托父加载器没有加载成功,则通过findClass(String)查找。

如果要编写一个classLoader的子类,也就是自定义一个classloader,建议覆盖findClass()方法,而不要直接改写loadClass()方法。

自定义ClassLoader

不管是Bootstrap ClassLoader还是ExtClassLoader等,这些类加载器都只是加载指定的目录下的jar包或者资源。当我们想从一些自定义的地方加载类的时候,譬如D盘或者某个url,就需要自定义一个classloader。

分三步走:

  • 编写一个类继承自ClassLoader抽象类。
  • 复写它的findClass()方法。
  • 在findClass()方法中调用defineClass()。defineClass不需要自己写,你需要管的只是从哪里取,即findClass

defineClass():这个方法在编写自定义classloader的时候非常重要,它能将class二进制内容转换成Class对象,如果不符合要求的会抛出各种异常。

其他

Context ClassLoader

每个Thread都有一个相关联的ClassLoader,默认是AppClassLoader。并且子线程默认使用父线程的ClassLoader除非子线程特别设置。Java 应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过此类加载器来加载类和资源。

这个类最初是为了解决SPI的问题,Java提供了很多SPI给三方实现,Java在核心库中使用这些SPI的时候,由于SPI 的接口是 Java 核心库的一部分,是由引导类加载器来加载的;SPI 实现的 Java 类一般是由系统类加载器来加载的。引导类加载器是无法找到 SPI 的实现类的,因为它只加载 Java 的核心库。它也不能代理给系统类加载器,因为它是系统类加载器的祖先类加载器。

线程上下文类加载器正好解决了这个问题。如果不做任何的设置,Java 应用的线程的上下文类加载器默认就是系统上下文类加载器。在 SPI 接口的代码中使用线程上下文类加载器,就可以成功的加载到 SPI 实现的类。

URLClassLoader

ClassLoader提供了一个URLClassLoader实现类,该类是系统类加载器和扩展类加载器的父类。

URLClassLoader(URL[] urls):使用parent(父类)加载器创建一个ClassLoader对象,该对象将从urls所指定的系列路径查询并获取类。
URKClassLoader(URL[] urls,ClassLoader paremt):使用指定父类加载器创建一个ClassLoader对象。

Class.forName 另一种加载类的方法