1 类加载过程
虚拟机只加载程序执行时所需要的类文件。假设程序从 MyProgram.class 开始运行,下面是虚拟机执行的步骤:
- 虚拟机有一个用于加载类文件的机制。
- 如果类拥有类型为另一个类的域,或者是拥有超类,那么这些类文件也会被加载。
- 接着,虚拟机执行类中的 main 方法。
- 如果 main 方法或者 main 调用的方法要用到更多的类,那么接下来就会加载这些类。
每个Java程序至少拥有三个类加载器:
- 引导类加载器
加载系统类。必含,通常 C 语言实现。没有对应的 ClassLoader 对象。 - 扩展类加载器
加载 jre/lib/ext 目录下“标准的扩展” - 系统类加载器
加载应用类
2 类加载器的层次结构
类加载器有一种父/子关系。除了引导类加载器外,每个类加载器都有一个父类加载器。父类加载失败子类才加载。
如果插件被打包为 JAR 文件,那就可以直接用 URLClassLoader 类的实例去加载这些类。
URL url = new URL("path");
URLClassLoader pluginLoader = new URLClassLoader(new URL[] {url});
Class<?> cl = pluginLoader.loadClass("mypackage.MyClass");
可以通过下面将其设置成为任何类加载器
Thread = Thread.currentThread();
t.setContextClassLoader(loader);
助手方法可以获取这个上下文类加载器:
Thread t = Thread.currentThread();
ClassLoader loader = t.getCOntextClassLoader();
Class cl = loader.loaderClass(className);
当调用由不同的类加载器加载的插件类的方法时,进行上下文类加载器的设置是一种好的思路;或者,让助手方法的调用者设置上下文类加载器。
3 将类加载器作为命名空间
同一虚拟机中,可以有两个类的类名和包名都相同。类是由它的全名和类加载器来确定的。
4 编写自己的类加载器
编写自己的类加载器,只需要继承 ClassLoader 类,然后覆盖下面这个方法
findClass(String className)
ClassLoader 超类的 loadClass 方法用于将类的加载操作委托给其父类加载器去进行,只有当该类尚未加载并且父类加载器也无法加载该类时,才调用 findClass 方法。
如果要实现该方法,必须做到以下几点:
- 为来自本地文件系统或者其他来源的类加载其字节码
- 调用 ClassLoader 超类的 defineClass 方法,想虚拟机提供字节码。
5 字节码校验
当类加载器将新在加载的 Java 平台类的字节码传递给虚拟机,这些字节码首先要接受校验器的校验。校验器负责检查那些指令无法执行的明细那有破坏性的操作。出了系统类外,所有的类都要被校验。
下面是校验器执行的一些检查:
- 变量要在使用之前进行初始化。
- 方法调用与对象引用类型之间要匹配。
- 访问私有类型和方法的规则没有被违反。
- 对本地变量的访问都落在运行时堆栈内。
- 运行时堆栈没有用溢出。