JVM 类加载机制:编译器常量与初始化

1. 前言

最近在研究JVM虚拟机类加载机制的时候,我们了解到了类加载机制的生命周期以及在准备阶段,JVM虚拟机会对类的静态变量进行初始化,这个时候只是会将静态变量初始化为默认的初始值。对静态变量的定义的初始化值,将会被封装到clinit()方法中,直到初始化阶段进行初始化。但是对于静态常量会使一个特例,下面我们将来看看JVM虚拟机对于静态常量是如何进行操作的。

2.类加载机制

下面我们再来复习一下JVM虚拟机类加载机制:

  • 加载:这是由类加载器执行的,该步骤将查找字节码(通常在classpath路径查找,但不是必须的),并从这些字节码中创建一个Class对象;
  • 链接:在链接阶段验证类的字节码,为静态域分配存储空间,并且如果必要的话,会解析这个类创建的对其它类的所有引用;
  • 初始化:如果该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化块。

初始化阶段被延迟到了对静态方法或者非常数静态域进行首次引用时才执行:

下面我们来看一下以下代码:

import java.util.Random;
class Initable{
 static final int staticFinal = 45;
 static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);
 static{
  System.out.println("Initializing Initable");
  System.out.println("Initable---------------");
 }
}
class Initable2{
 static int staticNonFinal = 145;
 static{
  System.out.println("Initializing Initable2");
  System.out.println("Initable2---------------");
 }
}
class Initable3{
 static int staticNonFinal = 54;
 static{
  System.out.println("Initializing Initable3");
  System.out.println("Initable3---------------");
 }
}
public class ClassInitialization {
 public static Random rand = new Random(45);
 public static void main(String[] args) throws ClassNotFoundException {
  Class initable = Initable.class;
  System.out.println("After Creating Initable Ref");
  System.out.println(Initable.staticFinal);
  System.out.println("------------------");
  System.out.println(Initable.staticFinal2);
  System.out.println(Initable2.staticNonFinal);
  Class initable3 = Class.forName("com.chenxyt.java.practice.Initable3");
  System.out.println("After Creating Initable3 Ref");
  System.out.println(Initable3.staticNonFinal);
 }
}

运行结果:
After creating Initable ref
47
Initializing Initable
258
Initializing Initable2
147
Initializing Initable3
After creating Initable3 ref
74

3. 结果分析

初始化有效的实现了尽可能的“惰性”。从结果中可以看出.class并不会引发初始化,相反使用Class.forName()时则立刻完成了初始化的功能。静态常量与初始化过程总结以下规则:

  1. 如果一个static final是“编译器常量”,就像Initable.staticFinal那样,那么这个值不需要对Initable进行初始化就可以被读取。因为它在准备阶段变量的初始值就会被直接初始化,具体原因是由于拥有final字段的静态常量在它的字段属性表中会出现ConstantValue属性。如果只是将一个域设置为static和final时,还不足以确保这种行为。并且一个static final不能确定在编译期间就能确定它的值,则它还不能确保这种行为。比如static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);
  2. 如果一个static域不是final的,那么对它进行访问时,总是要求在它被读取之前,要先进行链接(为这个域分配存储空间)和初始化(初始化该存储空间),就像在Initable2.staticNonFinal的访问中看到的那样。

猜你喜欢

转载自blog.csdn.net/qq_21125183/article/details/85037867