JVM学习笔记二

149 阅读4分钟

类的加载过程基础知识


前人栽树,后人乘凉。关于类加载的五个过程(加载、验证、解析、准备、初始化)前辈已总结的很好。
原文链接:blog.csdn.net/carson_ho/a…
原文链接:juejin.cn/post/684490…
自己小结的笔记

类加载机制

面试题一:类加载机制基础知识考察

1.简要谈一谈Java类加载过程
加载-验证-准备-解析-初始化-使用-卸载。
加载:通过类的全限定名获取二进制字节流并加载到方法区中,转换为虚拟机内存中一个实实在在的对象。class对象在堆中,作为访问方法区的入口。
验证:判断二进制字节流是否符合class文件规范,是否能在当前版本的JVM虚拟机上运行。主要验证class文件格式、元数据、字节码验证、符号引用验证。
准备:为类变量分配内存并设置类变量的值。
解析:将常量池中的符号引用解析成直接引用。
初始化:按顺序执行static代码块
2.谈一谈何时进行初始化
如上图
3.谈一谈ClassLoader有哪些?
如上图
4.谈一谈双亲委派机制及其好处?
口语化就是:我爸是李刚,有事找我爹。当加载类的时候,会一层一层网上捅,如果BootStrapClassLoader没有则在依次往下寻找。好处就是防止类被污染,导致出现多份名字相同的字节码文件。

面试题二:理解类的加载顺序

示例一:分析下列程序输出结果?

public class test_4 extends Father {

    public static void main(String[] args) {
        System.out.println(Son.age);
    }
}

class Son extends Father {
    static {
        System.out.println("儿子的静态方法");
    }
}

class Father extends GrandPa {
    public static int age = 20;

    static {
        System.out.println("爸爸的静态方法");
    }
}

class GrandPa {
    static {
        System.out.println("爷爷的静态方法");
    }
}

输出结果为:

爷爷的静态方法
爸爸的静态方法
20

解析:直接调用Son.age,此时并没有初始化Son类,但调用了Father类的静态变量age,所以会初始化Father类以及GrandPa类.

示例二:分析下列程序运行结果

public class Book {
    public static void main(String[] args)
    {
        staticFunction();
    }

    static Book book = new Book();

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

    {
        System.out.println("书的普通代码块");
    }

    Book()
    {
        System.out.println("书的构造方法");
        System.out.println("price=" + price +",amount=" + amount);
    }

    public static void staticFunction(){
        System.out.println("书的静态方法");
    }

    int price = 110;
    static int amount = 112;
}


输出结果为:

书的普通代码块
书的构造方法
price=110,amount=0
书的静态代码块
书的静态方法

分析:首先找main方法所在的类,初始化Book类, 加载-验证-准备-解析-初始化。到准备阶段为类变量在方法区中分配内存和初始化值,所以book = null, amount = 0;接着继续初始化,初始化过程中按顺序执行所有的静态代码块和类变量赋值语句,过程中遇到new Book()实例化了一个book对象,于是执行对象构造器,输出普通代码块中、Book()中的内容,实例化完成后继续类的初始化,即输出“书的静态块”,最后在为amount赋值为112,再去执行main方法中的内容。

示例三:分析下列程序运行结果

public class test_6 {
    private static test_6 t = new test_6();
    static int x;
    static int y=0;

    public test_6() {
        x++;
        y++;
    }

    public static void main(String[] args) {
        System.out.println(test_6.x);
        System.out.println(test_6.y);
    }
}

输出结果:
1
0


分析:找main方法,首先初始化main方法对应的类test_6,在加载-验证-准备-解析-初始化的准备阶段为类变量在方法区中分配内存和赋值,此时t=null、x=0、y=0;接着解析后进行初始化,初始化过程中按顺序执行所有的静态代码块和类变量赋值语句,执行到new test_6(),所以执行对象构造器,执行test_6(){....}此时x=1、y=1。在为y重新赋值为0,最后执行main方法中的语句,输出1、0

分析类的加载顺序的方法:

  1. 先找main方法对应的类,去初始化该类
  2. 加载-验证-准备-解析-初始化,留意准备阶段,在准备阶段时为类变量(static修饰的部分)在方法区分配内存并进行赋值
  3. 初始化类构造器,按顺序执行静态代码块和类变量赋值语句。若在初始化过程中实例化了类,则转去先执行对象构造器,等执行完毕在继续执行类构造器。
  4. 最后执行main方法中的内容。

参考链接:www.cnblogs.com/chanshuyi/p…