从此秒杀类加载面试题
JVM执行class文件步骤
1.加载
2.验证
3.准备
4.解析
5.初始化
6.使用
7.卸载
先看一个面试题,问题:执行以下代码会输出什么内容
public class Book { public static void main(String[] args) { System.out.println("Hello ShuYi."); } Book() { System.out.println("书的构造方法"); System.out.println("price=" + price +",amount=" + amount); } { System.out.println("书的普通代码块"); } int price = 110; static { System.out.println("书的静态代码块"); } static int amount = 112; }
重点说一下准备阶段和初始化阶段
准备
经过字节码文件的加载和验证后,准备阶段会对静态变量进行初始化。
private static int number = 10;
这个过程中 number 会被分配内存,同时将其初始化为0,这里不是直接赋值为10,这个过程而是在初始化步骤中完成的。但是有以下特殊情况需要特殊考虑
private static final int number = 10;
这个时候 number 会被赋值为10,因为final的特性使得初始化阶段不能改变,所以准备阶段会被直接赋值。
初始化
初始化的话分为两种类型:类初始化、对象初始化
遇到以下5种情况会执行类的初始化流程
- 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译器把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
- 使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
- 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
- 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
- 当使用 JDK1.7 动态语言支持时,如果一个 java.lang.invoke.MethodHandle实例最后的解析结果 REF_getstatic,REF_putstatic,REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行初始化,则需要先出触发其初始化。
举个例子:
public class Book { public static void main(String[] args) { System.out.println("Hello ShuYi."); } Book() { System.out.println("书的构造方法"); System.out.println("price=" + price +",amount=" + amount); } { System.out.println("书的普通代码块"); } int price = 110; static { System.out.println("书的静态代码块"); } static int amount = 112; }
如果要运行这个类的main方法,这就对应了以上的第四种情况,那么是如何执行初始化流程的呢?
类初始化方法。编译器会按照其出现顺序,收集类变量的赋值语句、静态代码块,最终组成类初始化方法。类初始化方法一般在类初始化的时候执行。
static { System.out.println("书的静态代码块"); } static int amount = 112;
类初始化时,会依次执行以上代码,也就是这个时候类变量才会真正被赋值
对象初始化方法(只有在实例化该类时才会执行)。编译器会按照其出现顺序,收集成员变量的赋值语句、普通代码块,最后收集构造函数的代码,最终组成对象初始化方法。对象初始化方法一般在实例化类对象的时候执行。构造方法是最后执行的
{ System.out.println("书的普通代码块"); } int price = 110; Book() { System.out.println("书的构造方法"); System.out.println("price=" + price +",amount=" + amount); }
所以依过类初始化过程(没有对象初始化) 最终输出结果为
书的静态代码块
Hello ShuYi.
现在我能深刻体会到成员变量是属于对象的,因为没有实例化,不会给成员变量分配空间和赋值
来个不一样的,看题
class Grandpa { static { System.out.println("爷爷在静态代码块"); } } class Father extends Grandpa { static { System.out.println("爸爸在静态代码块"); } public static int factor = 25; public Father() { System.out.println("我是爸爸~"); } } class Son extends Father { static { System.out.println("儿子在静态代码块"); } public Son() { System.out.println("我是儿子~"); } } public class InitializationDemo { public static void main(String[] args) { System.out.println("爸爸的岁数:" + Son.factor); //入口 } }
类初始化Son对象,先要初始化其父类,所以依次输出
爷爷在静态代码块
爸爸在静态代码块
爸爸的岁数:25
这里并不会输出 Son的静态代码块
这是因为对于静态字段,只有直接定义这个字段的类才会被初始化(执行静态代码块) 因此通过其子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化。