Java虚拟机JVM类加载机制是Java程序运行的重要组成部分。在执行Java程序之前,虚拟机需要将程序所需的类加载到内存中,然后才能对程序进行解释执行。在这个过程中,虚拟机采用了特定的类加载机制,这种机制能够确保程序在运行时能够正常地使用所需的类库和资源。
Java虚拟机JVM类加载机制的完整攻略可以分为以下几个步骤:
1. 加载
当虚拟机需要加载类时,会首先到指定的类路径中查找该类的字节码文件。如果找到了该文件,则虚拟机会使用特定的类加载器对该类进行加载,并将其保存到内存中。如果没找到该文件,则会抛出ClassNotFoundException异常。
Java虚拟机JVM类加载机制中常用的一种类加载器是URLClassLoader。它能够从指定的URL路径中加载类文件。例如,我们可以通过以下代码创建一个URLClassLoader对象,并使用它加载指定的类文件:
URLClassLoader classLoader = new URLClassLoader(new URL[]{new URL("file:///path/to/classfile")});
Class<?> cls = classLoader.loadClass("com.example.MyClass");
2. 验证
在加载完类文件后,虚拟机会对其进行验证。这一过程会检查字节码文件的格式是否正确,并且会检查该类是否符合Java虚拟机的规范。例如,虚拟机会检查该类是否有超过65535个常量等。
如果该类未通过验证,则虚拟机会抛出VerifyError异常。这种异常通常意味着该类文件已经被篡改过,或者该类是由不受信任的代码生成的。
3. 准备
在验证通过后,虚拟机会为该类分配内存空间,并给类中的每个静态变量赋初值。这些初值通常都是默认值,例如:整型的为0,对象引用的为null等。
4. 解析
在准备过程中,虚拟机会为每个类中的符号引用(例如方法调用和字段访问)分配一个确定的内存地址。这个过程被称为解析。解析主要分为两个阶段:类解析和方法解析。
类解析是将每个类的符号引用转换为一个确定的内存地址,该地址指向该类在内存中的实际地址。这一过程需要检查该类是否已经被加载到内存中。如果没有,虚拟机会继续加载该类,并分配内存空间。如果已经加载,则直接将该类的内存地址返回。
方法解析是将每个方法的符号引用转换为一个指向该方法实际代码所在位置的内存地址。这一过程通常在方法调用之前进行。解析过程会查找该方法所在类的内存地址,并根据方法名和参数类型在该类中查找对应的方法实现。
5. 初始化
在解析完类中的所有符号引用后,虚拟机会执行该类的初始化方法。该方法通常是一个静态方法,用于执行一些初始化操作,例如为静态变量赋值和执行静态代码块等。如果该类没有定义初始化方法,则不进行该步骤。
示例一:
class MyClass {
public static void main(String[] args) {
System.out.println("Hello, world!");
}
}
以上为一个典型的Java程序,使用JVM时会先进行类加载,接下来我们就从class文件开始,具体说明一下JVM类加载机制的过程。
加载
JVM会在类路径下寻找 MyClass.class 文件,找到以后会把它读入内存,并产生一个对应的 Class 对象。如果找不到会抛出 ClassNotFoundException 错误。
验证
在类加载时,JVM会校验 MyClass.class 文件的字节码的正确性。如果校验不通过,会抛出 VerifyError 错误。
准备
如果 MyClass 类中有静态成员变量或者静态代码块,JVM 会为它们在内存中分配空间并设置默认的初始值(int 类型为 0,对象类型为 null)。
解析
JVM 会把 MyClass 类中所有符号引用全部加以解析,如在该类中使用了其他类,JVM 会递归调用这些类的加载(加载、验证、准备、解析,最后初始化),直接完全解析完成后,所有的符号引用都会映射到一个具体的内存地址,从而准确地指向对应的代码。
初始化
在解析完所有符号引用之后,JVM 会执行 MyClass 类的初始化方法。在本例中,只有 main 方法是静态的,所以它会被执行。
示例二:
class MyGreeting {
static String greeting = "Hello";
static {
greeting = greeting + ", world!";
}
}
class MyClass {
public static void main(String[] args) {
System.out.println(MyGreeting.greeting);
}
}
以上为一个稍复杂的 Java 程序,MyGreeting 类有一个静态属性 greeting,和一个静态代码块,会对这个属性赋值。在 main 方法中,我们打印出 MyGreeting.greeting 的值,看看会输出什么。
加载
JVM 会在类路径下每个类的包名对应的文件夹中寻找每个类的 class 文件,这时 MyGreeting 类会被先加载,然后 MyClass 类会被加载。
验证
和之前一样,对 MyClass.class 和 MyGreeting.class 文件的字节码进行校验,保证其正确性。
准备
因为 MyGreeting 类中有静态成员变量 greeting,JVM 会分配这个变量空间,并将它默认值(null),然后开始初始化这个类。注意,初始化只会执行一次,即使初始化代码被定义在多个静态代码块中。
解析
MyGreeting 类中的静态代码块会被执行,更新 greeting 的值为 "Hello, world!"。
初始化
MyGreeting 类初始化完成,MyClass 开始执行 main 方法。main 中虽然没有任何定义,但是打印语句中使用到了 MyGreeting 类,JVM 会保证静态代码块被执行,并将正确的值输出(Hello, world!)。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java虚拟机JVM类加载机制(从类文件到虚拟机) - Python技术站