【语言学习】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对象。