JVM类加载机制

为了春招,系统地复习一下Java相关的知识,应该不会写源码解析,完全是给自己看的,如果足够幸运,有幸进入大厂,重新写下给别人看的

注意点

  • 类从被加载到被卸载的生命周期如下: 加载 -> (验证 -> 准备 -> 解析)(连接) -> 初始化 -> 使用 -> 卸载 ; 虽然写下来是这样的的顺序,但是不同阶段间可能交叉进行,即一个阶段还未完成,另一个就已经开始.而解析这个阶段却有例外,在某些情况下,解析可以再初始化后进行,如果了解了解析这个阶段是干什么的后,其实就明白初始化后解析就是Java中的动态绑定.

  • 加载

    • 该阶段主要做三件事:
      • 通过一个类的全限定名找到此类的二进制字节流
      • 将字节流所代表的静态存储结构转换为方法区的运行时数据结构
      • 在内存中生成一个这个类的java.lang.Class对象,这个对象不放在堆中,而是在方法区中,主要作为第二步生成的各种数据结构的访问入口.
    • 类加载机制
      • 主要说一下如何将字节流转换成Class对象,也就是我们经常说的类加载器,这里就要说很多,我之前写过一个关于双亲委托机制的博客,这里
  • 验证

    • 这个阶段就是为了确保Class文件的字节流中包含的内容符合当前虚拟机的要求,不会危害到虚拟机自身的安全.详细的我没有深入了解过.
  • 准备

    • 这个阶段主要是为类中静态变量设置初始值,注意,并不是程序中的初始值,而是该类型的默认值,即整数置0,而不是程序中的.但是还是有例外.如果这个静态变量是用final修饰的,就会被置程序中的初始值.
  • 解析

    • 解析这个阶段就是将常量池中的符号引用转变为直接引用,其实我并不是很理解符号引用,我只是当做变量和方法和类之间的绑定,这个理解并不正确,只是暂且如此.
  • 初始化

    • 到了这一步,其实才真正的和用户相关,因为之前的阶段我们除了可以指定自定义类加载器,其余都是虚拟机自主的,
    • 这一步就会对准备阶段"初始化"的静态变量进行真正的初始化,赋予程序中的初始值.
    • 这个阶段其实是执行类构造器方法的过程,这个方法会收集所有类变量和静态语句块
  • 类加载时机,这是我之前的疑惑,一直没找到准确的描述,最近在书上才找到.Java虚拟机其实并没有对于什么时候开始加载这个阶段做严格要求,完全交给虚拟机自己把握.

  • 类初始化时机,上面说到Java没有对加载时机做要求,但是对于初始化却做了严格要求,即主动使用一个类时进行初始化,有主动自然由被动,详情参看主动使用和被动使用,当然了,初始化前一定要进行加载,验证,准备阶段.

  • 还需要注意的一点是静态内部类,静态内部类和静态属性和静态方法还是有一些不同.
    先看一些相同的,从主动使用和被动使用的博客中可以看到,访问静态属性和静态方法都属于主动使用,会触发类的初始化,同样的,访问一个类的静态内部类也会引起该类的初始化,而且是先外部后内部.
    不同的,其实也不能算是不同,这么说吧,如果外部的类被初始化时并不会触发静态内部类的初始化.

示例

看以下代码

public class Father {
    private int i = test() ;
    private static int j = method();

    static {
        System.out.print("(1) ");
    }


    public Father(){
        System.out.print("(2) ");
    }
    {
        System.out.print("(3) ");

    }
    private int test() {
        System.out.print("(4) ");
        return 0;
    }

    private static int method() {
        System.out.print("(5) ");
        return 0;
    }
}

class Son extends Father{
    private int i = test() ;

    private static int j = method();


    static {
        System.out.print("(6) ");
    }
    public Son(){
        System.out.print("(7) ");
    }
    {
        System.out.print("(8) ");

    }
    private int test() {
        System.out.print("(9) ");
        return 0;
    }

    private static int method() {
        System.out.print("(10) ");
        return 0;
    }


    public static void main(String[] args) {
    }
}

我们根据main函数中的语句分析类初始化和实例化过程:

  1. main中没有任何语句: 输出 : (5) (1) (10) (6),解释一下,因为有入口(main)函数,使得Son这个类进行了初始化,初始化的过程,上面有所提及,会对静态变量赋值,执行静态代码块的内容,至于这两个哪个先执行,取决于代码中的位置;然后就是父类子类的问题,子类的初始化必定会触发父类的初始化,所以会有子类和父类的初始化;
  2. main中有以下代码:Son son = new Son();结果是什么呢,如下: (5) (1) (10) (6) (4) (3) (2) (9) (8) (7);分析一下:首先是类的初始化,然后就是实例话触发的东西; 还是先实例化父类,4 , 3 , 2代表什么,首先执行对象成员变量的实例化,然后是非静态代码块,最后是构造器;
  3. main中有一下代码:
        Son son = new Son();
        System.out.println();
        Son son1 = new Son() ;

输出是这样的:

(5) (1) (10) (6) (4) (3) (2) (9) (8) (7) 
(4) (3) (2) (9) (8) (7) 

很明显,当创建两个实例对象的时候,类的初始化只进行了一次,而实例初始化进行了两次,如果看字节码文件的话,会发现两个标记,前者是类初始化方法,后者是实例化方法,前者只有一个,而后者可以有很多;

你可能感兴趣的:(面试准备)