前言
类的加载其实就是将.class文件加载的jvm的内存之中。在JVM中并不是一次性把所有的文件都加载到,而是一步一步的,按照需要来加载。JVM启动时会通过不同的类加载器加载不同的类,而且同一个类也不可能由多个加载器来进行加载。正是这种分级加载策略,才能保证各个类在jvm中有条不紊的运行。下面来和大家一起深入学习下java中的类加载。
父类委托机制
字节码文件是通过JVM中的加载器加载到JVM中,继而初始化为对象。在jvm中大致有下列这四种加载器,分别是
-
BootStrapClassLoader 引导类加载器,它负责加载 JAVA_HOME\lib 目录中的,或通过-Xbootclasspath参数指定路径中的,且被虚拟机认可(按文件名识别,如rt.jar)的类。对于BootStrapClassLoader ,其实这个ClassLoader完全由JVM自己控制,需要加载哪个类,怎么加载都是由JVM自己决定。别人是访问不了这个类的,所以一般而言,它主要用来加载JVM自身工作需要的类
-
ExtClassLoader 扩展类加载器,它负责加载 JAVA_HOME\lib\ext 目录中的,或通过java.ext.dirs系统变量指定路径中的类库。
-
AppClassLoader 应用类加载器 ,它负责加载用户路径(classpath)上的类库。
-
CustomClassLoader 用户自定义类加载器,对于用户自定义的加载器,不管你是直接实现ClassLoader,还是继承URLClassLoader,或者其他的子类。它的父类加载器都是AppClassLoader ,因为不管调用哪个父类构造器,创建的对象都必须最终调用getSystemClassLoader()作为父加载器,而getSystemClassLoader()方法获取到的正是AppClassLoader 。
上面几个加载器的关系可以用下图来表示
当JVM运行过程中,用户需要加载某些类时,会按照下面的步骤(父类委托机制):
-
用户自己的类加载器,把加载请求传给父加载器,父加载器再传给其父加载器,一直到加载器树的顶层。
-
最顶层的类加载器首先针对其特定的位置加载,如果加载不到就转交给子类。
-
如果一直到底层的类加载都没有加载到,那么就会抛出异常ClassNotFoundException。
因此,按照这个过程可以想到,如果同样在CLASSPATH指定的目录中和自己工作目录中存放相同的class,会优先加载CLASSPATH目录中的文件。
如何加载Class文件
前面简单介绍了Class文件的加载机制,那么Class文件从加载过程到底经历了哪些过程呢?
其实可以大致分为下图的几个步骤
加载
加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的入口。注意这里不一定非得要从一个Class文件获取,这里既可以从ZIP包中读取(比如从jar包和war包中读取),也可以在运行时计算生成(动态代理),也可以由其它文件生成(比如将JSP文件转换成对应的Class类)。
验证
这一阶段的主要目的是为了确保Class文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
准备
准备阶段是正式为类变量分配内存并设置类变量的初始值阶段,即在方法区中分配这些变量所使用的内存空间。
解析
解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。
初始化
初始化阶段是类加载最后一个阶段,前面的类加载阶段之后,除了在加载阶段可以自定义类加载器以外,其它操作都由JVM主导。到了初始阶段,才开始真正执行类中定义的Java程序代码。
总结
上面简单介绍了JVM中的类加载机制,以及类加载过程中经历的几个步骤。了解清楚类加载机制对日常开发中遇到的一些异常的解决也是有帮助的,就看比如常见的ClassNotFoundException,NoClassDefFoundException。
参考文献:《深入分析java web技术内幕》
https://blog.csdn.net/qyp199312/article/details/65628283
http://www.importnew.com/25295.html
http://www.cnblogs.com/xing901022