术业专攻

类的加载、连接、初始化

2018-12-20  本文已影响853人  zhh_happig

转载、引用请标明出处
https://www.jianshu.com/p/853701433b3a
本文出自zhh_happig的简书博客,谢谢

以下内容,是本人学习笔记和工作中的总结,仅供大家参考,有误的地方还请指正

java虚拟机

执行一个java程序,都会启动一个java虚拟机的进程,进程里面包含一个主线程来执行程序,当程序执行完了之后,java虚拟机进程就消亡了。
在如下几种情况下,java虚拟机将结束生命周期

一 类的加载、连接、初始化

1 加载:查找并加载类的二进制数据

2 连接

3 初始化:为类的静态变量赋予正确的初始值

二 java程序对类的使用方式

主动使用
被动使用

主动使用(6种),除了以下6种,其他类的使用全是被动使用

所有的java虚拟机实现必须在每个类或接口被java程序 首次主动使用 时才初始化他们,换句话说就是以上6种情况,而且是第1次主动使用才会在初始化。其他情况,如被动使用,或第二次主动使用,都不会执行类的初始化。

调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化。调用ClassLoader类的loadClass方法只是执行了加载操作。

二 示例演示——增加理解

示例1

class Singleton{

    private static Singleton singleton = new Singleton();
    public static int counter1;
    public static int counter2 = 0;

    private Singleton(){
        counter1 ++;
        counter2 ++;
    }

    public static Singleton getInstance(){
        return singleton;
    }

}

public class JVMTest {

    public static void main(String[] args){
        Singleton singleton = Singleton.getInstance();
        System.out.println("counter1 = " + singleton.counter1);
        System.out.println("counter2 = " + singleton.counter2);
    }

}

输出结果
1
0

在类的连接--准备阶段,singleton被赋默认值null,counter1和counter2被赋默认值0;初始化的时候,先初始化singleton,创建实例,在Singleton 构造方法中counter1++,counter2++后,counter1和counter2变成了1,然后在再初始化counter1和counter2,counter1没有被赋值,所以还是1, counter2被赋值了0,所以counter2最终为0。

如果将上面的部分代码顺序改一下:

public static int counter1;
public static int counter2 = 0;
private static Singleton singleton = new Singleton();
输出结果
1
1
如果看懂了上面的代码,这个不难理解。

示例2

public class Test2 {

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

}

class FinalTest{

    public final static int x = 1/3;
    static{
        System.out.println("FinalTest static block");
    }

}

输出结果
0

final修饰的静态变量要注意:如果这个变量在编译时就能确定它的值,就不会导致类被初始化,例如
public static final int x = 1/3

public class Test2 {

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


}

class FinalTest{

    public final static int x = new Random().nextInt(100);
    static{
        System.out.println("FinalTest static block");
    }

}

输出结果
FinalTest static block
75

如果这个变量要在运行时才能确定它的值,才会导致类被初始化,例如
public static final int x = new Random().nextInt(100)

示例3

public class Test3 {

    static {
        System.out.println("Test3 static block");
    }

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

}

class Parent{

    static int a = 3;
    static{
        System.out.println("Parent static block");
    }

}


class Child extends Parent{

    static int b = 4;
    static{
        System.out.println("Child static block");
    }

}

输出结果?
Test3 static block
Parent static block
Child static block
4

Test3是程序入口类,最先被加载初始化;先加载初始化父类,再子类。

示例4

public class Test4 {

    static {
        System.out.println("Test4 static block");
    }

    public static void main(String[] args){
        Parent2 parent2;
        System.out.println("-------------");

        parent2 = new Parent2();
        System.out.println(Parent2.a);
        System.out.println(Child2.b);
    }

}

class Parent2{

    static int a = 3;
    static{
        System.out.println("Parent2 static block");
    }

}


class Child2 extends Parent2{

    static int b = 4;
    static{
        System.out.println("Child2 static block");
    }

}

输出结果
Test4 static block
-------------
Parent2 static block
3
Child2 static block
4

Child2、Parent2是由同一个类加载器加载的,所以Parent2初始化了, Child2初始化的时候就不会再去初始化父类了。
PS: 如果有两个加载器:A类的加载器,B类的加载器,AB不是父子关系,即使A类的加载器已经初始化了Child2类,在B类的加载中还是可以再去初始化Child2类的。详解请看后续的 Java类加载器 文章

为什么下面这一行代码不去初始化Parent2呢?

...
Parent2 parent2;
...

因为这只是声明了一个变量,并没有主动使用类,所以不会初始化。

示例5

public class Test5 {

    public static void main(String[] args){
        System.out.println(Child3.a);
        Child3.doSomething();
    }

}

class Parent3{

    static int a = 3;
    static{
        System.out.println("Parent3 static block");
    }

    static void doSomething(){
        System.out.println("doSomething");
    }

}


class Child3 extends Parent3{

    static{
        System.out.println("Child3 static block");
    }

}

输出结果?
Parent3 static block
3
doSomething

Child3.a,Child3.doSomething() 为什么Child3没有别初始化?
只有当访问的静态变量或静态方法确实在当前类或当前接口中定义时,才可以认为是对类和接口的主动使用。而Child3.a,Child3.doSomething()调用的静态变量或静态方法不是在Child2类中定义的,而是在父类中定义的,所以不会对Child2进行初始化。

示例6

class C{

    static {
        System.out.println("class C");
    }

}

public class Test1 {

    public static void main(String[] args) throws Exception{
        ClassLoader loader = ClassLoader.getSystemClassLoader();
        Class<?> clazz = loader.loadClass("com.zhh.jvm.loadClass.C");//加载 C 类
        System.out.println("------------");
        clazz = Class.forName("com.zhh.jvm.loadClass.C");
    }

}

输出结果?
------------
class C

loader.loadClass("com.zhh.jvm.loadClass.C");
调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化。调用ClassLoader类的loadClass方法只是执行了加载操作。clazz = Class.forName("com.zhh.jvm.loadClass.C");是反射创建类的实例,是类的主动使用,所以导致类被初始化。

以上内容,是本人学习笔记和工作中的总结,仅供大家参考,有误的地方还请指正

转载、引用请标明出处
https://www.jianshu.com/p/853701433b3a
本文出自zhh_happig的简书博客,谢谢

上一篇 下一篇

猜你喜欢

热点阅读