Java中类的加载步骤和过程
Java类的加载过程是JVM将类的字节码文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被JVM直接使用的Java类型的过程。这个过程主要分为以下三个阶段:
1. 加载(Loading)
主要任务:
- 通过类的全限定名获取定义此类的二进制字节流
- 将字节流所代表的静态存储结构转换为方法区的运行时数据结构
- 在内存中生成一个代表该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
加载来源:
- 本地文件系统(最常见的.class文件)
- JAR、ZIP等归档文件
- 网络加载(Applet)
- 运行时计算生成(动态代理)
- 由其他文件生成(JSP文件)
- 从专有数据库中提取
2. 链接(Linking)
链接阶段又分为三个子步骤:
2.1 验证(Verification)
确保加载的类信息符合JVM规范,不会危害JVM安全:
- 文件格式验证(魔数、版本号等)
- 元数据验证(语义分析)
- 字节码验证(程序逻辑)
- 符号引用验证(常量池中的引用)
2.2 准备(Preparation)
为类变量(static变量)分配内存并设置初始值:
- 此时进行内存分配的仅包括类变量(static修饰的变量)
- 初始值通常是数据类型的零值(如0、0L、null、false等)
- 如果类变量是常量(final static),则会直接赋值为代码中指定的值
2.3 解析(Resolution)
将常量池内的符号引用替换为直接引用的过程:
- 类或接口的解析
- 字段解析
- 类方法解析
- 接口方法解析
3. 初始化(Initialization)
主要任务:
- 执行类构造器
<clinit>()
方法(由编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生) - 初始化类变量为代码中指定的值
- 如果该类有父类,JVM会保证父类的
<clinit>()
先执行
初始化触发条件(有且只有以下5种情况):
- 遇到new、getstatic、putstatic或invokestatic这4条字节码指令时
- 使用java.lang.reflect包的方法对类进行反射调用时
- 当初始化一个类时,发现其父类还未初始化时
- JVM启动时,用户指定的主类(包含main()方法的类)
- JDK7+的动态语言支持,MethodHandle实例最后的解析结果为REF_getStatic等时
类加载的完整过程
- 检查类是否已加载 → 已加载则直接返回
- 若未加载,委托父加载器尝试加载
- 父加载器无法加载时,调用自身的findClass方法加载
- 加载完成后执行链接过程(验证→准备→解析)
- 最后执行初始化过程
类加载器的双亲委派模型
-
启动类加载器(Bootstrap ClassLoader):
- 加载JAVA_HOME/lib目录下的核心类库
- 由C++实现,是JVM的一部分
-
扩展类加载器(Extension ClassLoader):
- 加载JAVA_HOME/lib/ext目录下的类
- Java实现,sun.misc.Launcher$ExtClassLoader
-
应用程序类加载器(Application ClassLoader):
- 加载用户类路径(ClassPath)上的类库
- Java实现,sun.misc.Launcher$AppClassLoader
-
自定义类加载器:
- 用户自定义的类加载器
- 继承java.lang.ClassLoader类
双亲委派机制工作流程:
- 类加载器收到加载请求后,首先不会自己尝试加载,而是委托给父类加载器
- 只有当父加载器反馈无法完成加载请求时,子加载器才会尝试自己加载
优点:
- 避免类的重复加载
- 保证Java核心API的安全(防止核心类被篡改)