如下图所示,是ClassLoader加载一个class文件到JVM时需要经过的步骤:
第一阶段是找到.class文件并把这个文件包含的字节码加载到内存中。 第二阶段又可以分为三个步骤,分别是字节码验证、Class类数据结构分析及相应的内存分配和最后的符号表的链接。 第三阶段是类中静态属性和初始化赋值,以及静态块的执行。
加载字节码到内存
其实在抽象类ClassLoader中并没有定义如何去加载,如何去找到指定类并且把它的字节码加载到内存需要在子类中去实现,也就是要实现findClass()
方法。看下在URLClassLoader中如何实现findeClass的,在URLClassLoader中通过一个URLClassPath类帮助取得要加载的class文件字节流,而这个URLClassPath定义了到哪里去找这个class文件,如果找到了这个class文件,再读取它的byte字节流,然后通过调用defineClass方法来创建类对象。
源码如下:
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
final Class<?> result;
try {
result = AccessController.doPrivileged(
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
String path = name.replace('.', '/').concat(".class");
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
return null;
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
if (result == null) {
throw new ClassNotFoundException(name);
}
return result;
}
我们再来看一下URLClassLoader类的构造函数,我们发现,必须要指定一个URL数据才能够创建URLClassLoader对象,也就是必须要指定这个ClassLoader默认到哪个目录下去查找class文件。
代码语言:javascript复制 public URLClassLoader(URL[] urls) {
super();
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
ucp = new URLClassPath(urls);
this.acc = AccessController.getContext();
}
这个URL数组也是创建URLClassPath对象的必要条件。从URLClassPath的名字中就可以发现它是通过URL的形式来表示ClassPath路径的。 在 创建URLClassPath对象时,会根据传过来的URL数据中的路径来判断时文件还是jar包,根据路径的不同分别创建FileLoader或者JarLoader,或者使用默认的加载器。当JVM调用findeClass时这几个加载器来将class文件的字节码加载到内存中。 如何设置每个ClassLoader的搜索路径呢? 下表是BootStrap ClassLoader、ExtClassLoader和AppClassLoader的参数形式
classLoader类型 | 参数选项 | 说明 |
---|---|---|
BootStrap ClassLoader | -Xbootclasspath: | 设置BootStrap ClassLoader的搜索路径 |
ExtClassLoader | -Xbootclasspath/a: -Xbootclasspath/p: | 把路径添加到已存在BootStrap ClassLoader搜索路径的后面(a)和前面(p) |
AppClassLoader | -Djava.ext.dirs | 设置AppClassLoader的搜索路径 |
AppClassLoader | -Djava.class.path= -cp或-classpath | 设置AppClassLoader的搜索路径 |
在上面的参数设置中,最常用到的就是设置classpath 的环境变量,因为通常都是让Java运行指定的程序。如果在通过命令执行一个类时出现NoClassDefFoundError错误,那么很可能是没有指定classpaht所致,或者指定了classpath但是没有指明包名。
验证与解析
- 字节码验证,类装入器对于类的字节码要做许多检测,以确保格式正确、行为正确。
- 类准备,在这个阶段准备代表每个类中定义的字段、方法和实现接口所必需的数据结构。
- 解析,在这个阶段,类装入器装入类所引用的其他所有类。可以用许多方式引用类,如超类、接口、字段、方法签名、方法中使用的本地变量。
初始化class对象
在类中包含的静态初始化器都被执行,在这一阶段末尾静态字段被初始化为默认值。