理解类的加载时机与初始化

上一篇博客中(讲解面试题那一篇)只是从运行结果的表象来分析了java中类的加载时机,近两天走马观花的读了周志明老师的《深入理解java虚拟机》一书,再来回答java类的加载时机。下面的回答大部分会引用原书,加少量自己的实践。

在周老师的书中,虚拟机类的加载机制为一大章,然后分分为三小节具体深入讲解①.类的加载时机②.类的加载过程(加载.验证.准备.解析.初始化)③.类加载器(类与类加载器.双亲委派模型.破坏双亲委派模型)。这种经典书籍希望大家也可以买来细细品读。这次我们只说类的加载时机。

原书中这样说:
①.遇到new.getstatic.putstatic或invokestatic这四条字节指令码时,如果没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的java代码场景是:使用new关键字初始化对象的时候、读出或设置一个类的静态字段_(被final修饰、已经在编译时期把结果放入常量池的静态字段除外)_的时候,调用一个类的静态方法的时候。
②.使用java.lang.reflect包的方法对类进行反射调用的时候,如果没有进行过初始化,则需先触发其初始化。
③.当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
④.当虚拟机启动时,用户还需要指定一个需要执行的主类(包含main()方法的那个类),虚拟机会先去初始化这个主类。
⑤.当使用jdk1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个句柄所对应的类没有进行过初始化,则需先触发其初始化。
对于这5种会触发类进行初始化的场景,虚拟机规范中使用了一个很强烈的限定语:“有且只有”,这5种场景中的行为称为对一个类进行主动引用。除此之外引用类的方式都不会触发初始化,称为被动引用。下面举三个例子来说明何为被动引用,分别说明:

代码1:

class SuperClass {
	static {
		System.out.println("SuperClass init!");
	}

	public static int value = 123;
}

class SubClass extends SuperClass {
	static {
		System.out.println("SubClass init!");
	}
}

public class NotInitialization {

	public static void main(String[] args) {
		System.out.println(SubClass.value);
	}
}
上述代码执行后,只会输出“SuperClass init!”,而不会输出“SubClass Init!”。对于静态字段,只有直接定义这个字段的类才会别初始化,因此通过其子类来引用父类中的静态字段,只会触发父类的初始化而不会触发子类的初始化。

代码2:

class SuperClass {
	static {
		System.out.println("SuperClass init!");
	}

	public static int value = 123;
}

class SubClass extends SuperClass {
	static {
		System.out.println("SubClass init!");
	}
}

public class NotInitialization {

	public static void main(String[] args) {
		SuperClass[] sca = new SubClass[10];
	}
}
上述代码执行后并没有输出“SuperClass init!”,说明并没有触发SuperClass的初始化阶段。

代码3:

上述代码执行后,也没有输出“ConstClass init!”,这是因为虽然在java源码中引用了ConstClass类中的常量HEELLOWORID,但其实在编译阶段通过常量传播优化,已经将其常量的值“Hello world” 存储到了 NotInitialization类的常量池中,以后NotInitialization对常量的ConstClass。HELLOWORLD的引用世界都转化为NotInitialization类对自身常量池的引用了。也就是说,实际上NotInitialization的Class文件之中zaimeiyouConstClass类的符号引入口,这两个类在编译成Class之后就不在有任何联系了。
上面三个代码来看,以前对类的加载时机误解还是真不少,比如类的加载与类的初始化根本就是两回事,读者可以在前两个代码的主函数中加入如下代码来查看java虚拟机中已经加载进来的类。
		Field f=ClassLoader.class.getDeclaredField("classes");
        f.setAccessible(true);
        Vector classes=(Vector)f.get(ClassLoader.getSystemClassLoader());
        System.out.println(classes);
前两个代码,用到的三个类都加载进了内存,只是部分类没有初始化,最后一个代码只有含有主函数的类加载进了内存。初始化,粗劣的可以理解为:对加载进来的类可以执行,变量的赋值和静态代码块等的执行。(具体请参看原书)

也就是说只要记住周老师书中所说的那五种有且仅有的原因,其他的情况都不会对类进行初始化。

关于周老师书中说的五点,我来细化一点,关于第二条。

第二条:看如下代码:
class Dog {
	static {
		System.out.println("Dog init!");
	}
}

public class ReflectInitializtion {

	public static void main(String[] args) throws Exception {
		Class.forName("com.test.Dog");
	}
}
此时会把Dog类加载进内存,并进行初始化。
修改代码后
class Dog {
	static {
		System.out.println("Dog init!");
	}
}

public class ReflectInitializtion {

	public static void main(String[] args) throws Exception {
		Class clazz = Dog.class;
	}
}

此时只会加载Dog进入内存,并不会进行初始化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值