类的初始化过程
基本概念
类加载:在java代码中,类型(class,enum,interface)的加载、连接和初始化过程都是在程序运行期间完成的。这样提供了更大的灵活性,增加了更多的可能性
类加载器:JAVA源程序=》javac编译=》字节码文件.class=》JVM=》装载ClassLoader=》运行时数据区=》执行引擎,本地方法库接口=====》本地方法库
JVM基本结构:类加载器,执行引擎,运行时数据区,本地接口
类的装载:
加载:查找并加载类的二进制数据
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在内存中创建一个java.lang.Class对象(规范并未说明Class对象位于哪里,在HotSpot虚拟机将其放在了方法区中)用来封装类在方法区内的数据结构
连接
验证:确保被加载的类的正确性
准备:为类的静态变量分配内存,并将其初始化为默认值
解析:把类中的符号引用转换为直接引用
初始化:为类的静态变量赋予正确的初始值
Java虚拟机实现必须在每个类或接口被Java程序首次主动使用时才初始化它们。(对于什么时候类加载,java虚拟机规范中并没有进行强制约束,这点可以交给虚拟机的具体实现来自由把我,但是对于初始化阶段,虚拟机严格对定了以下几种情况)
JAVA程序对类的使用方式可分为两种:主动使用(六种),被动使用
主动使用
1)创建类的实例,也就是new Object
2)访问某个或接口的静态变量,或者对静态变量赋值
3)访问类的静态方法
4)反射调用类
5)初始化一个类的子类
6)Java虚拟机启动时被表明为启动类的类(如一个类中含有main方法)
其他使用java类的方式都被看作为被动使用
主动使用样列
1、初始化一个类的子类回先初始化它的父类,但是初始化父类,子类并不会被初始化,需要牢牢把握主动使用的六种
public class Test1 {
public static void main(String[] args) {
//当只执行下面这条语句的时候,子类的static静态代码块并不会运行,也就是没有初始化,
//因为对于类的初始化只有首次主动使用时才会初始化
/**
* 输出
* MyParent1 static block
* hello world
*/
System.out.println(MyChild1.str);
// System.out.println("=======================================");
// System.out.println(MyChild1.str2);
}
}
class MyParent1 {
public static String str = "hello world";
static {
System.out.println("MyParent1 static block");
}
}
class MyChild1 extends MyParent1 {
public static String str2 = "welcome";
static {
System.out.println("MyChild1 static block");
}
}
2、对于final常量的初始化过程
/**
* 常量在编译阶段会存入到调用这个常量的方法所在类的常量池中,
* 本质上,调用类并没有直接引用到定义常量的类,因此并不会触发定义常量的类的初始化
* 注意:这里指的时将常量存存放到Test2的常量池中,之后Test2与MyParent2就没有任何关系了,
* 甚至我们可以将MyParent2的Class文件删除
*/
public class Test2 {
public static void main(String[] args) {
//不会初始化MyParent2类
System.out.println(MyParent2.str); //这里只会输出hellword,并不会导致MyParent2被初始化
}
}
class MyParent2 {
public static final String str = "hello world";
static {
System.out.println("MyParent2 static block");
}
}
/**
* 当一个常量的值并非编译器期间不可以确定的,那么其值就不会被放到调用该类的常量池中
* 这时在程序运行时,会导致主动使用这个常量所在的类,显然会导致这个类被除初始化
*/
public class Test3 {
public static void main(String[] args) {
//由于str的值是编译期不能确定值的,所以会导致MyParent3被初始化
System.out.println(MyParent3.str);//这里就回导致MyParent3被初始化
}
}
class MyParent3 {
//该值是在编译期间是不知道的,只有当运行时才能确定
public static final String str = UUID.randomUUID().toString();
static {
System.out.println("MyParent3 static code");
}
}
3、对于数组的初始化过程
/**
* 对于数组实例来说,其类型是由JVM在运行期动态生成的,表示为[Lcom.chen.jvm.MyParent4
* 这种形式。动态生成的类型,其父类型就是Object
*/
/**
* 对于数组的加载javadoc
* 数组类的加载器
* <p> <tt>Class</tt> objects for array classes are not created by class
* loaders, but are created automatically as required by the Java runtime.
* The class loader for an array class, as returned by {@link
* Class#getClassLoader()} is the same as the class loader for its element
* type; if the element type is a primitive type, then the array class has no
* class loader.
*/
public class Test4 {
public static void main(String[] args) {
//并不是主动使用,也就不会导致MyParent4类的初始化
MyParent4[] myParent4 = new MyParent4[1];
//[Lcom.chen.jvm.MyParent4; 这个类型是java虚拟机帮助我们动态生成的类型
System.out.println(myParent4.getClass());//class [Lcom.chen.jvm.MyParent4;
System.out.println(myParent4.getClass().getSuperclass());//class java.lang.Object
MyParent4 myParent5 = new MyParent4();//MyParent4 statci code
System.out.println(myParent5.getClass());//class com.chen.jvm.MyParent4
int[] ints = new int[1];
System.out.println(ints.getClass());//class [I
System.out.println(ints.getClass().getSuperclass());//class java.lang.Object
}
}
class MyParent4 {
static {
System.out.println("MyParent4 statci code");
}
}
4、接口父子初始化过程
/**
* 当一个接口初始化时,并不要求其父接口都完成初始化
* 只有在真正使用到父接口的时刻(如引用接口中所定义的常量时),才会初始化
*/
public class Test5 {
public static void main(String[] args) {
//也就是MyParent5接口并不会被初始化
System.out.println(MyChild5.b); //6
}
}
interface MyParent5 {
static int a = 5;
}
interface MyChild5 extends MyParent5 {
static int b = 6;
}
5、静态变量初始化过程
public class Test6 {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
System.out.println(Singleton.counter1); // 输出1
//若new Singleton()放在counter1于counter2下面的话,也会输出1
//这个实例可以典型说明类的初始化过程是先将静态变量赋予默认值,然后再赋正确的值这一过程
System.out.println(Singleton.counter2); // 输出0
}
}
class Singleton {
private static Singleton singleton = new Singleton();
public static int counter1;
public static int counter2 = 0;
// private static Singleton singleton = new Singleton();
public Singleton() {
counter1++;
counter2++;
}
public static Singleton getInstance(){
return singleton;
}
}