Java类加载机制

何为类加载?

类加载是指将类的.class文件读入内存,再将其放入运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区的数据结构。

类的生命周期

加载——>|验证——>准备——>解析|——>初始化——>使用——>卸载
其中验证-准备-解析被称为连接(Linking)

类的加载过程

包含了加载、验证、准备、解析和初始化这5个阶段。

1. 加载

加载是类加载的一个阶段,注意不要混淆。加载过程完成以下三件事:

  • 通过一个类的全限定名来获取定义此类的二进制字节流。
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时存储结构
  • 在堆内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口(封装了这个类的各种数据和操作)。

JVM规范对于类的二进制流,没指明要从class文件中获取。开发团队提供了相当开放的“舞台”:

  • 从 ZIP 包读取,成为 JAR、EAR、WAR 格式的基础。
  • 从网络中获取,最典型的应用是 Applet。
  • 运行时计算生成,例如动态代理技术,在java.lang.reflect.Proxy 使用 ProxyGenerator.generateProxyClass的代理类的二进制字节流。
  • 由其他文件生成,例如由 JSP 文件生成对应的Class类。

2. 验证

确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

  • 文件格式验证
  • 元数据验证
  • 字节码验证
  • 符号引用验证

3. 准备

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区分配。

  • 实例变量不会在这阶段分配内存,它将会在对象实例化时随着对象一起分配在堆中。
  • 注意,实例化不是类加载的一个过程,类加载发生在所有实例化操作之前,并且类加载只进行一次,实例化可以进行多次。
  • 初始值一般为0值,例如以下类变量value初始值为0而不是123
public static int value = 123;

注:把value赋值为123的动作将在初始化阶段执行!

  • 如果**类变量是常量,那么会按照表达式来进行初始化,而不是赋值为0
public static final int value = 123;

基本数据类型的零值:
int---0;long---0L;short---(short)0; char---'\u0000';byte---(byte)0;boolean---false;float---0.0f;double---0.0d;reference---null.

4. 解析

  • 将常量池的符号引用替换为直接引用的过程。
  • 其中解析过程在某些情况下可以在初始化阶段之后再开始,这是为了支持 Java 的动态绑定。

5. 初始化

初始化阶段才真正开始执行类中定义的Java程序代码。初始化阶段即虚拟机执行类构造器()方法的过程。

在准备阶段,类变量已经赋过一次系统要求的初始值,而在初始化阶段,根据程序员通过程序制定的主观计划去初始化类变量和其它资源。

什么时候进行初始化

JVM规定下列5种情况需要初始化类

  • 遇到new、getstatic、putstatic或者invokestatic这4条字节码指令(new新建对象、读取或设置一个类的静态字段和调用一个类的静态方法时)
  • 使用java.lang.reflect包的方法对类进行反射调用时
  • 初始化一个类时,父类未初始化则先初始化父类
  • 虚拟机启动时要初始化一个用户指定执行的主类(包含main()方法)
    -JDK1.7后对动态语言的支持,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getstatic、REF_putstatic、REF_invokestatic的方法句柄,并且这个方法句柄对应的类没有进行初始化,则需要先触发其初始化

你可能感兴趣的:(Java类加载机制)