JVM(Java Virtual Machine),即Java虚拟机。Java之所以拥有跨平台的能力,JVM在其中有着举足轻重的地位。

JVM的类加载机制是指:JVM把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。

其实整个加载过程,主要有三大步,分别是:加载、链接、初始化。接下来,我将分别进行阐述。


第一步、加载

首先从不同的数据源(可以是class文件、jar包,网络、数据库等等符合class文件结构的来源)将二进制字节码加载到JVM内存中,并映射为JVM可用的数据结构(Class对象)。如果加载的不是Class文件类型,则会抛出java.lang.ClassFormatError。

加载阶段的可控性非常强,我们开发人员可以使用系统提供的相关类加载器(启动类加载器、扩展类加载器和应用程序类加载器),也可以自定义类加载器来实现我们想要的功能,比如阿里的热修复框架AndFix就利用自定义类加载器来加载修复包文件。


第二步、链接

在这一步,又可以细分为:验证、准备、解析三个阶段。

  • 验证

    对加载进来的二进制字节码进行验证,检查是否符合JVM的规范,如果验证失败,则会抛出java.lang.VerifyError。该操作可有效保障JVM的安全,防止进行恶意操作。

  • 准备

    创建类或接口中的静态变量,并初始化静态变量的初始值,以及在方法区分配所需的内存空间。

    上面所讲的静态变量是指被static修饰的变量,不包括实例变量,如下代码:

public static int result = 666;

该静态变量经过准备阶段之后,初始值是0,而不是666。因为这里只分配了内存,并没有执行其他的JVM指令。但是也有一种特殊情况,如下代码:

public static final int result = 666;

此时,result被final修饰,*.java文件经过Javac编译后,就会在该字段的属性表中生成ConstantValue属性,并在准备阶段的时候,JVM就会根据ConstantValue的设置将result赋值为666。

  • 解析

    在这一步JVM会将常量池中的符号引用转换为直接引用。

这里的符号引用和直接引用的区别:

符号引用:符号引用是以一组符号来描述所引用的目标,符号并没有具体的命名规范要求,只要没有歧义就行。与JVM实现的内存布局无关,并且引用的目标不一定存在于内存中。

直接引用:直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。与JVM实现的内存布局相关,同一个符号引用在不同JVM实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那所引用的目标必定存在于内存中。


第三步、初始化

这一步就正式开始执行类中定义的代码块,包括静态变量的赋值。到此,JVM类加载的整个过程就完成了。


最后,简要谈一下双亲委派模型,其目的是为了防止重复加载Java类型。

当前类加载器试图加载某个类时,会优先将当前任务抛给父加载器来完成,除非父加载器没找到相应的类型,最终才由该类加载器来完成该任务。

结语:我这里只是对JVM类加载机制进行了一个大概的总结,更详细的介绍可阅读周志明著的《深入理解Java虚拟机:JVM高级特性与最佳实践》一书。


收藏 打印