java中类加载classLoader时机(啥时间开始的?)

java虚拟机规范虽然没有强制性约束在什么时候开始类加载过程,但是对于类的初始化,虚拟机规范则严格规定了几种情况必须立即对类进行初始化,如果类没有进行过初始化,则需要先触发其初始化。在触发初始化是会有加载和连接(验证,准备,解析)阶段(不懂的看这个文章:https://blog.csdn.net/xiao1_1bing/article/details/81120787)。
生成这几种最常见的java代码场景是也称为主动使用:

1)使用new关键字实例化对象

2)访问类的静态变量,包括读取一个类的静态字段 和 设置一个类的静态字段 (除常量【 被final修辞的静态变量】 原因:常量一种特殊的变量,因为编译器把他们当作值(value)而不是域(field)来对待。如果你的代码中用到了常变量(constant variable),编译器并不会生成字节码来从对象中载入域的值,而是直接把这个值插入到字节码中。这是一种优化机制,叫编译器编译优化,对这个机制不理解可以看这两篇文章,文章1:https://blog.csdn.net/xiao1_1bing/article/details/81131837和文章2:https://blog.csdn.net/xiao1_1bing/article/details/81131300)

3)访问类的静态方法

4)反射 如( Class.forName(“my.xyz.Test”) )

5)当初始化一个类时,发现其父类还未初始化,则先出发父类的初始化

6)虚拟机启动时,定义了main()方法的那个类先初始化 

验证:

1、对于第一种:new创建实例对象

class ClassLoadTime{
  static{
    System.out.println("ClassLoadTime类初始化时就会被执行!");
  }
  public ClassLoadTime(){
    System.out.println("ClassLoadTime构造函数!");
  }
}

class ClassLoadDemo{
  public static void main(String[] args){
    ClassLoadTime  clt = new ClassLoadTime();
  }
}

先为第六种先初始化含有main()方法的ClassLoadDemo类,然后会加载static的main方法,同时发现第一种情况的要实例化(new)ClassLoadTime,(类在使用New调用创建的时候会被java类装载器装入)就需要初始化ClassLoadTime类,那么就会在该类的准备阶段为静态变量(类变量:为啥叫类变量?因为可以用类.静态变量名调用)和静态块分配空间和设置默认值。就会调用static块,最后因为new出的classLoadTime类参数为空,就会调用参数为空的构造函数最终生成对象。

输出结果:

 ClassLoadTime类初始化时就会被执行!

 ClassLoadTime构造函数!


 

2、 对于第二种的第一个:访问类的静态变量--读取一个类的静态字段

class ClassLoadTime{
  static{
    System.out.println("ClassLoadTime类初始化时就会被执行!");
  }
  public static int max = 200; (防止测试类和此类不在一个包,使用public修饰符)
  public ClassLoadTime(){
    System.out.println("ClassLoadTime构造函数!");
  }
}

class ClassLoadDemo{
  public static void main(String[] args){
    int  value = ClassLoadTime.max;
    System.out.println(value);
  }
}

前面和上面一样,直到用类.静态变量,它会初始化ClassLoadTime类,初始化该类之前会有准备阶段:为static分配空间和设置默认值,就会调用static块和static变量。到了初始化阶段会将代码中的200赋值给max,所以会打印出200。因为并没有实例化该类,所以不会调用无参的构造方法,虚拟机只进行了类加载并没有生成对象,因此非静态成员(实例成员)并不会进行初始化,而静态成员(类成员)则被初始化。不new是不会调用构造器的。

输出:

ClassLoadTime类初始化时就会被执行!

200


 

    对于第二种的第二个:访问类的静态变量--设置一个类的静态字段

class ClassLoadTime{
  static{
    System.out.println("ClassLoadTime类初始化时就会被执行!");
  }
  public static int max = 200; (防止测试类和此类不在一个包,使用public修饰符)
  public ClassLoadTime(){
    System.out.println("ClassLoadTime构造函数!");
  }
}

class ClassLoadDemo{
  public static void main(String[] args){
      ClassLoadTime.max = 100;
  }
}

这个和上面一样,但是该ClassLoadTime在初始化阶段将200赋值给max,但在使用阶段又将100赋值给静态变量max。

输出:

ClassLoadTime类初始化时就会被执行!


       被final修饰静态字段在操作使用时,不会使类进行初始化,因为在编译期已经将此常量放在常量池。

测试:

class ClassLoadTime{
  static{
    System.out.println("ClassLoadTime类初始化时就会被执行!");
  }
  public static final int MIN = 10; (防止测试类和此类不在一个包,使用public修饰符)
}

class ClassLoadDemo{
  public static void main(String[] args){
   System.out.println(ClassLoadTime.MIN);
  }
}

由于编译器编译优化,导致该变量在常量池中,当你用ClassLoadTime.MIN时,只会到常量池中查该数值,不会加载ClassLoaderTime类。

输出:

10


3、对于第三种:调用一个类的静态方法

class ClassLoadTime{
  static{
    System.out.println("ClassLoadTime类初始化时就会被执行!");
  }
  public static int max = 200; (防止测试类和此类不在一个包,使用public修饰符)
  public ClassLoadTime(){
    System.out.println("ClassLoadTime构造函数!");
  }
  public static void method(){
    System.out.println("静态方法的调用!");
  }
}

class ClassLoadDemo{
  public static void main(String[] args){
      ClassLoadTime.method();
  }
}

这个和第二种情况有些类似,只要你调用类的静态方法,它的静态代码块和静态变量会在准备阶段分配内存和设置默认值。然后会调用静态方法。但是该类没有实例化,所以不会调用无参的构造方法。

输出:

ClassLoadTime类初始化时就会被执行!

静态方法的调用!

同时,会不会想到如果实例化(new)静态块和静态变量会被调用,但静态方法是否会被调用?看下面例子。

class ClassLoadTime{
    static{
        System.out.println("ClassLoadTime类初始化时就会被执行!");
    }
    public static int max = 200; //(防止测试类和此类不在一个包,使用public修饰符)
    public ClassLoadTime(){
        System.out.println("ClassLoadTime构造函数!");
    }
    public static void method(){
        System.out.println("静态方法的调用!");
    }
}

class ClassLoadDemo{
    public static void main(String[] args){
        ClassLoadTime c =new ClassLoadTime();
    }
}

输出:

ClassLoadTime类初始化时就会被执行!
ClassLoadTime构造函数!

由此可见,除了对静态方法显示调用外,其他都不会调用静态方法。


 

4、对于第四种:反射

class ClassLoadTime{
    static{
        System.out.println("ClassLoadTime类初始化时就会被执行!");
    }
    public static int max = 200; //(防止测试类和此类不在一个包,使用public修饰符)
    public ClassLoadTime(){
        System.out.println("ClassLoadTime构造函数!");
    }
    public static void method(){
        System.out.println("静态方法的调用!");
    }
}

class ClassLoadDemo{
    public static void main(String[] args) throws ClassNotFoundException {
        Class.forName("ClassLoadTime");
    }
}

对于反射只会初始化该类,准备阶段将静态块和静态变量进行分配空间和设置默认值,其他都不会做。

输出:

ClassLoadTime类初始化时就会被执行!


 

5、对于第五种:子类访问父类的静态字段或者调用父类的静态方法时仅仅初始化父类,而不初始化子类。同样读取final修饰的常量不会进行类的初始化。

class Fu{
  public static int value = 20;
  static{
    System.out.println("父类进行了类的初始化!");
  }
}

class Zi extends Fu{
  static{
    System.out.println("子类进行了类的初始化!");
  }
}

class LoadDemo{
  public static void main(String[] args){
    System.out.println(Zi.value);    
  }
}

通过上面的了解,基本已经掌握类的初始化步骤和顺序,但是对于继承关系来说,实例化子类的顺序是这样的(虽然调用父类构造函数但是不生成父类实例,因为如果父类是抽象类,那么它是不会被实例化的,但会调用构造函数):

先初始化父类的静态代码—>初始化子类的静态代码–>初始化父类的非静态代码—>初始化父类构造函数—>初始化子类非静态代码—>初始化子类构造函数

先将各自的静态代码加载进来,再返回到父类去加载其他的。但是当子类只调用父类静态的话,是不会调用子类的类加载,也不会调用父类的构造方法,只会初始化静态成员(静态块和静态方法)。

输出:

父类进行了类的初始化!

20

解析下这段代码:

class SingleTon {
  private static SingleTon singleTon = new SingleTon();
  // count1=1,count2=1
  public static int count1;
  // count1=1,count2=1(没进行初始化)
  public static int count2 = 0;
  // count1=1,count2=0

  private SingleTon() {
    count1++;
    count2++;
  }

  public static SingleTon getInstance() {
    return singleTon;
  }
}

public class Test {
  public static void main(String[] args) {
    SingleTon singleTon = SingleTon.getInstance();
    System.out.println("count1=" + singleTon.count1);
    System.out.println("count2=" + singleTon.count2);
  }
}

结果:

count1=1

count2=0

分析原因

(对此不理解的看类加载的准备阶段和初始化阶段,地址:https://blog.csdn.net/xiao1_1bing/article/details/81120787):

  1. SingleTon singleTon = SingleTon.getInstance();调用了类的SingleTon调用了类的静态方法,触发类的初始化
  2. 类加载的时候在准备过程中为类的静态变量分配内存并初始化默认值 singleton=null count1=0,count2=0
  3. 类初始化化,为类的静态变量赋值和执行静态代码快。singleton赋值为new SingleTon()调用类的构造方法
  4. 调用类的构造方法后count=1;count2=1
  5. 继续为count1与count2赋值,此时count1没有赋值操作,所有count1为1,但是count2执行赋值操作就变为0

 java类中各种成员的初始化时机,此处不一一测试:

类变量(静态变量)、实例变量(非静态变量)、静态代码块、非静态代码块 的初始化时机:
(1)* 由 static 关键字修饰的(如:类变量[静态变量]、静态代码块)将在类被初始化创建实例对象之前被初始化,而且是按顺序从上到下依次被执行,static变量和static代码块优先级别是一样的,所以安装从上到下顺序; 

 public static int value =34;
   static{
  System.out.println("静态代码块!");
 }

 public 类名(){    
  System.out.println("构造函数!");
 }

   一旦这样写,在类被初始化创建实例对象之前会先初始化静态字段value,然后执行静态代码块,当实例化对象时会执行构造方法中的代码
(2)* 没有 static 关键字修饰的(如:实例变量[非静态变量]、非静态代码块)初始化实际上是会被提取到类的构造器中被执行的,但是会比类构造器中的代码块优先执行,其也是按顺序从上到下依次被执行。

 public int value =34;
 {
  System.out.println("非静态代码块!");
 }

 public 类名(){    
  System.out.println("构造函数!");
 }

在使用构造函数实例化一个对象时,会先初始化value,然后执行非静态代码块,最后执行构造方法里面的代码。

(3)*在存在父类的时候,调用子类的构造时,会先调用父类的默认构造(空参构造),进行父类的初始化,虽然调用了父类的构造函数,但是没有生成父类的实例。

被动引用例子(不会发生类加载初始化的情况)

  1. 子类调用父类的静态变量,子类不会被初始化。只有父类被初始化。 对于静态字段,只有直接定义这个字段的类才会被初始化.
  2. 通过数组定义来引用类,不会触发类的初始化,因为只是类的引用,还没有生成类的实例。
  3. 访问类的常量,不会初始化类,final修饰的

你可能感兴趣的:(JVM)