java虚拟机要运行一段程序,需要加载编译器编译好的.class文件,也就是常说的类的加载。
1、什么是类的加载?
类的加载时JVM读取一个.class文件到内存,根据文件中全限定名在方法区生成类的数据结构,再在堆内存上生成一个与之对应的java.lang.Class对象,此对象封装了类在方法区的数据结构,并向java程序员提供访问方法区数据结构的接口。
2、类的生命周期
类的生命期为:加载、验证、准备、解析、初始化、使用和卸载。前面5大步骤是类加载的部分。
3、类的加载机制
类加载器使用双亲委派机制。
目前java存在三种类加载器:
1、启动类加载器(BookstrapClassLoader)
启动类加载器是顶层类加载器,java中所有类的都需要先通过它进行加载。它是JVM本身自带的加载器,负责加载java自身的一些类。
2、扩展类加载器(ExtClassloader)
该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载DK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器。
3、应用类加载器
该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
应用程序都是由这三种类加载器互相配合进行加载的,如果有必要,我们还可以加入自定义的类加载器。因为JVM自带的ClassLoader只是懂得从本地文件系统加载标准的java class文件,因此如果编写了自己的ClassLoader,便可以做到如下几点:
1)在执行非置信代码之前,自动验证数字签名。
2)动态地创建符合用户特定需要的定制化构建类。
3)从特定的场所取得java class,例如数据库中和网络中。
双亲委派模型,即每一个加载器都有一个父类加载器(启动类加载器除外),此处的父类加载器不是使用继承关系而是组合。当类加载器加载一个类的时候,首先会让其父类加载器去加载,直到最顶层加载器(BookstrapClassLoader),启动类加载器如果找不到这个类,那么就交给其子加载器(ExtClassloader),扩展类加载器尝试去加载时,如果还是不能找到这个类,以此直到成功加载这个类或者报ClassNotFound异常。可以从java源码中看到这种机制:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
此外,我们还可以自己自定义类加载器,需要继承自java中的抽象类ClassLoader并重写其findClass()方法,一般不要重写其loadClass()方法,可能会破坏类加载的双亲委派机制。重写findClass方法一般可能就是特殊的二进制文件需要自己处理,其实主要就是读取二进制流数据并处理。
注:如果要使用自定义的加载器,那么.class文件不能放在classpath目录下,否则会被应用加载器加载。