从此秒杀类加载面试题

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种情况会执行类的初始化流程

  1. 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译器把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
  2. 使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
  3. 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
  4. 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
  5. 当使用 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方法,这就对应了以上的第四种情况,那么是如何执行初始化流程的呢?

  1. 类初始化方法。编译器会按照其出现顺序,收集类变量的赋值语句、静态代码块,最终组成类初始化方法。类初始化方法一般在类初始化的时候执行。

    static
     {
         System.out.println("书的静态代码块");
     }
    
     static int amount = 112;

    类初始化时,会依次执行以上代码,也就是这个时候类变量才会真正被赋值

  2. 对象初始化方法(只有在实例化该类时才会执行)。编译器会按照其出现顺序,收集成员变量的赋值语句、普通代码块,最后收集构造函数的代码,最终组成对象初始化方法。对象初始化方法一般在实例化类对象的时候执行。构造方法是最后执行的

     {
         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的静态代码块
这是因为对于静态字段,只有直接定义这个字段的类才会被初始化(执行静态代码块) 因此通过其子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化。

这里需要重点记住


本次内容就这么多,碰到以上题目按套路见招拆招就好了

#笔试题型#
全部评论
看了这个,内心更充实了,希望面试时不再害怕
点赞 回复 分享
发布于 2022-08-14 21:08

相关推荐

Vincent777...:实习经历可以考虑放上去,对于软件使用方面可以细化一些,比如调整为:熟悉基于LSDYNA的瞬态动力学仿真分析,熟悉基于WORKBENCH的结构拓扑优化
我的简历长这样
点赞 评论 收藏
分享
评论
3
6
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务