Java的类加载机制是指在Java程序运行时,将类文件加载到内存中的一系列步骤。Java的类加载机制遵循着“按需加载”的原则,也就是说,只有在需要用到某个类的时候,才会将这个类的相关信息加载到内存中。这种“按需加载”的设计使得Java程序具备了很好的灵活性和效率。
Java的类加载器主要分为三类:启动类加载器、扩展类加载器和应用程序类加载器。不同的类加载器负责加载不同的类,在Java的类加载机制中,具有不同级别的权限和不同的类搜索路径。下面我们来详细的介绍一下Java的类加载机制。
1.加载机制
Java的类加载机制主要分为三个过程:加载、连接和初始化。这三个过程的顺序是固定的,但是每个过程中的细节却是不同的。下面我们来详细介绍一下这三个过程。
1.1 加载
Java的类加载器会根据类的全限定名来加载类,当需要使用某个类时,如果该类还未被加载进内存,则需要执行一下步骤进行加载:
1.1.1. 通过类的全限定名找到对应的class文件,这里的class文件可以是.java文件经过编译之后生成的.class文件,也可以是通过其他方式生成的.class文件。
1.1.2 将class文件中的二进制数据读取到内存中,并将其转换为方法区的运行时数据结构。
1.1.3 创建由该类所属的java.lang.Class对象。该对象可以理解为,是对类的各种数据(如名称、访问修饰符、方法、成员变量等)的封装。
在加载类时,类加载器除了加载某个具体的类外,还需要将这个类所依赖的类也加入到内存中。这种依赖性是多层级的,也就是说,被依赖的类又可能会去依赖其他类,所以在加载一个类时,通常需要将其类图中所有的类都加载进来。
1.2 连接
Java虚拟机在加载类之后,需要对类进行连接,连接分为三个步骤:验证、准备和解析。
1.2.1. 验证:在这个步骤中,Java虚拟机主要确保所加载的类的正确性。验证过程主要包括文件格式验证、元数据验证、字节码验证和符号引用验证等。其目的在于确保目标.class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机运行时环境安全。
1.2.2. 准备:在准备阶段,Java虚拟机为类的静态变量分配内存,并设置变量的初始值。这里需要注意的是,在这个阶段中分配的内存并不包含那些用户自定义的初始化值,这些值在初始化阶段中进行设置。
1.2.3. 解析:Java在这个阶段中将常量池中的符号引用转为直接引用。通过符号引用,虚拟机得知该类访问其他的类或者类中的字段、方法等,但在类初始化时,需要缓存这些直接引用,以便于直接调用。
1.3 初始化
在类的准备阶段,Java虚拟机已经为静态变量分配了内存并设置了初值,但是这些静态变量”赋初值“的动作并没有完成。初始化阶段,会为静态变量设置用户自定义的初始化值,并执行类构造器<clinit>()方法,以执行初始化操作。
此时,类的准备和初始化阶段已经执行结束,Java的类加载机制总的过程也就结束了。
2.三种类加载器
Java类加载器为了适应Java程序的变化和复杂性,把不同的加载器负责不同的类,组成一个层次化的结构,这样就产生了三种不同级别的类加载器:启动类加载器、扩展类加载器和应用程序类加载器。
2.1 启动类加载器
启动类加载器是Java虚拟机中最基础的类加载器,它主要负责加载第一个被虚拟机执行的特殊类——java.lang.ClassLoader,该类在虚拟机启动时被加载,并提供了所有其它类加载器的基础,也就是说,所有的类加载器都直接或者间接地继承自这个类。
启动类加载器由C 语言实现,并不是Java语言实现。此外,在Java中也无法通过代码获取到该类加载器对象的引用。
2.2 扩展类加载器
扩展类加载器是用来加载Java系统扩展库的类加载器,负责加载$JAVA_HOME/lib/ext目录下的类库。扩展类加载器是在启动类加载器之后启动的,它也是纯Java代码实现的类加载器。
在Java中,扩展类加载器的父加载器是启动类加载器。这种方式保证了系统类库在运行时的稳定性和安全性。
2.3 应用程序类加载器
应用程序类加载器是应用程序中默认的类加载器。它主要负责加载应用程序classpath下的类,也就是我们平时自己写的Java代码所编译出来的.class文件。
应用程序类加载器是在扩展类加载器之后启动的,也是纯Java实现的类加载器。由于应用程序类加载器在Java中属于最高的加载器级别,所以我们通常也称之为“系统类加载器”。
应用程序类加载器的父类加载器是扩展类加载器,父类加载器负责加载Java系统库中的类。当应用程序类加载器无法加载一个类时,会向上委托其父类加载器进行加载,直到启动类加载器都找不到才会抛出ClassNotFoundException异常。
3.类搜索路径
Java的类加载器在执行加载、连接、初始化的过程时,会按照一定的顺序从类搜索路径中查找类文件。对于每个类加载器,都有一个对应的类搜索路径。下面我们来讲解一下各个类加载器的类搜索路径。
3.1 启动类加载器的搜索路径
启动类加载器使用的是C 语言实现的类加载器,由于其不是Java语言实现的,所以该类加载器的类搜索路径并不是由Java的类路径控制的,而是默认的系统搜索路径,因此并不容易进行调整。
3.2 扩展类加载器的搜索路径
扩展类加载器和应用程序类加载器等都是使用Java语言实现的类加载器,它们的类搜索路径可以通过系统属性来指定。扩展类加载器的搜索路径在$JAVA_HOME/lib/ext文件夹下。
3.3 应用程序类加载器的搜索路径
应用程序类加载器的搜索路径是通过应用程序传入的classpath参数来设置的。可以通过以下方式来查看应用程序类加载器的搜索路径:
代码语言:javascript复制String property = System.getProperty("java.class.path");
String[] paths = property.split(":");
for (String path : paths) {
System.out.println(path);
}
这里需要注意的是,在使用classpath参数指定类的搜索路径时,可以同时指定多个目录和.jar/.zip文件。对于多个目录和文件,使用分隔符来分割,Windows系统上分隔符使用“;”分割,Linux系统上使用“:”分割。
4.双亲委派机制
Java的类加载机制采用的是双亲委派模型,这是一种经典的类加载机制,也是Java的类加载机制得以广泛使用的重要原因之一。
所谓“双亲委派”,就是在类加载的过程中,一个类加载器在加载某个类时,会先委托给它的父类加载器进行加载。如果父类加载器无法加载成功,再由自己来加载该类。这样做的好处在于,保证Java虚拟机安全、稳定地运行,避免出现恶意代码污染系统。
在Java中,类加载器会一直询问其父加载器是否能够加载某个类,直到达到启动类加载器(即基类加载器)时,如果该类还没有被加载,此时才交由启动类加载器去加载这个类。因为启动类加载器是由Java虚拟机自身实现的,任何类都可以使用它来进行加载,所以启动类加载器就成为了Java虚拟机中的“保护神”。
双亲委派机制可以防止Java程序中出现重复的类,保障了上层的ClassLoader不会出现被下层的ClassLoader覆盖的情况,从而保护了Java程序的安全性。