1.类加载的过程
首先从整体的角度来理解JVM,一个java类从编译到运行的整个过程如下:
其中,一个java类被编译成.class文件后,并最终加载到JVM中运行前,经历了“加载”、“连接”、“初始化”的过程,其中连接过程又包括:“验证”、“准备”、“解析”。每一步的作用如下:
- 1.加载 - 根据类的全限定名获取类的二进制流,并转化为JVM的方法区的运行时数据结构,最后实例化一个java.lang.Class类的对象。
- 2.验证 - 确保导入数据的正确性,保证不危害到虚拟机自身的安全。
- 3.准备 - 为类变量分配内存,并将其初始化为默认值。
- 4.解析 - 把类型中的符号引用转换为直接引用。
- 5.初始化 - 类变量初始化为正确初始值。
2.双亲委派模型
从JVM的角度类加载器包括两类:Bootstrap ClassLoader,这个类是由C++语言实现的,这个是虚拟机自身的一部分;另一种就是所有其他的类加载器,这些是由Java语言实现的,独立于虚拟机外部。
从Java开发人员的角度来看,大家比较熟悉的包括:
- 1.启动类加载器(Bootstrap
ClassLoader):负责加载\lib目录中的类库。可以通过-Xbootclasspath参数指定 - 2.扩展类加载器(Extension
ClassLoader):负责加载\lib\ext目录下的类库,由sun.misc.Launcher$ExtClassLoader实现 - 3.应用类加载器(Application
ClassLoader):负责加载用户类路径(ClassPath)上指定的类库,由sun.misc.Launcher$AppClassLoader实现
双亲委派的模型如下:
3.ClassLoader的实现
上面讲了类加载的过程、常用的类加载器以及双亲委派模型,下面讲一下ClassLoader的具体实现,上面提到类加载器在sun.misc.Launcher中实现。我们以AppClassLoader为例,其继承关系如下:
- 1.ClassLoader:定义了类加载的核心操作。
- 2.SecureClassLoader:继承自ClassLoader,负责policy权限等支持。
- 3.URLClassLoader:继承自SecureClassLoader,支持从jar文件和文件夹中获取class
4.ClassLoader核心方法的实现
4.1 loadClass
loadClass方法在ClassLoader抽象类中实现,负责类的加载顺序。代码中可以看出
- 1.findLoadedClass方法判断类是否被加载过,若被加载过则直接返回被加载的类。
- 2.若未被加载过,则查看当前是否存在parent,若存在则优先由parent加载。
- 3.若不存在parent则由BootstrapClassLoader加载。
- 4.最后若parent类加载器未成功加载,则由当前的ClassLoader加载,调用的是findClass方法。
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;
}
}
4.2 getClassLoadingLock
其中采用同步锁的方式来解决并发的问题,而getClassLoadingLock方法中采用ConcreentHashMap来保证线程安全。同时,parallelLockMap可以控制是否进行并发加载,若其中的parallelLockMap为null则会将this锁住,无法进行并发加载。
protected Object getClassLoadingLock(String className) {
Object lock = this;
if (parallelLockMap != null) {
Object newLock = new Object();
lock = parallelLockMap.putIfAbsent(className, newLock);
if (lock == null) {
lock = newLock;
}
}
return lock;
}
4.3 findClass
findClass方法在ClassLoader中直接抛异常,因为findClass的方法不应该由ClassLoader这个抽象类来实现,而应该根据具体加载的资源不同,来实现自己的findClass,下面是常用的URLClassLoader方法,其中主要是获取资源后,即可调用defineClass方法来进行具体的类的加载。
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
try {
return 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 {
throw new ClassNotFoundException(name);
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
}
结束
此内容参考了《深入理解Java虚拟机》一书,源码部分简单讲了一下自己的理解,回顾一下以下几个点:
- 1.类的加载顺序:BootstrapClassLoader -> ExtClassLoader ->
AppClassLoader,这种实现是通过ClassLoader抽象类的parent优先加载实现的。 - 2.一个类被同一个ClassLoader只加载一次:通过同步获取getClassLoadingLock中的lock解决并发问题。
- 3.findClass:由具体的实现类来实现如何如何获取Class资源。
后续
后续中还会继续讲一下在具体web容器中,类的加载与我们JVM类加载器有哪些不同。