• JVM学习四:JVM之类加载器之初始化分析


    在经过了前面的加载  和 连接分析之后,这一节我们进入重要的初始化分析过程:

                   

    一、认识初始化

    初始化:这个似乎与上面的初始化为默认值有点矛盾,我们再看一遍:为累的静态变量赋予正确的初始值,上面是赋予默认值,这里是赋予正确的初始值,什么是正确的初始值,就是用户给赋予的值。我们来看一个例子

    class Test{
    private static int a = 1;
    
    }

    我们知道,这个类加载好之后,a的值就是1,但实际是这样子的,类在加载的连接阶段,将a初始化为默认值0(int的默认值是0),然后在初始化阶段将a的值赋予为正确的初始值1. 我们看到最终a的值是等于1,但是实际的运行中是有一个将0赋予a的过程,这个过程放生在连接的准备阶段。类的初始化还有另外的一种形式,代码如下:

    class Test{
    
    private static int a ;
    
    static{
    
    a=1;
    
      }
    
    }

    这里强调一点,这个时候还是没有类的实例生成的,这点一定要注意!《深入java虚拟机第二版》里面有一个图阐述了对应的关系,如下:

    静态变量的声明语句,以及静态代码块都被看做类的初始化语句,Java虚拟机会按照初始化语句在类文件中的先后顺序来依次执行它们。

    二、初始化步骤

    1.假如这个类还没有被加载和连接,那就先进行加载和连接。【加载和连接执行完成后不一定会执行初始化,要符合主动使用才会进行初始化,否则只会执行加载和连接】
    2.假如类存在直接的父类,并且这个父类还没有被初始化,那就先初始化直接的父类。
    3.假如类中存在初始化语句,那就依次执行这些初始化语句。

    三、主动使用和被动使用【初始化时机】

     Java程序对类的使用方式可分为2种,主动使用和被动使用。所有的Java虚拟机实现必须在每个类或接口被Java程序“首次主动使用时”才初始化他们。

    主动使用的六种情况:

    1.创建类的实例。

    new Test();

    2.访问某个类或接口的静态变量,或者对该静态变量赋值。

    int b = Test.a;
    Test.a = b;

    3.调用类的静态方法

    Test.doSomething();

    4.反射

    Class.forName(“com.mengdd.Test”);

    5.初始化一个类的子类

    class Parent{
    }
    class Child extends Parent{
          public static int a = 3;
    }
    Child.a = 4;

    6.Java虚拟机启动时被标明为启动类的类

    java com.mengdd.Test

    除了以上六种情况,其他使用Java类的方式都被看作是对类的被动使用,都不会导致类的初始化 (除了上述6种情况以外,都不会执行初始化,只会执行加载和连接)

    这个时候我们再来看第一节学习中那段比较诡异的代码:

    class Singleton{
    
       
    
        private static Singleton singleton=new Singleton();
    
        private static int counter1;
    
        private static int counter2 = 0;
    
       
    
        public Singleton() {
    
           counter1++;
    
           counter2++;
    
        }
    
        public static int getCounter1() {
    
           return counter1;
    
        }
    
        public static int getCounter2() {
    
           return counter2;
    
        }
    
    
        public static Singleton getInstance(){
    
           return singleton;
    
        }
    
       
    
    }
    
    
    public class ClassLoaderTest {
    
    
        @SuppressWarnings("static-access")
    
        public static void main(String[] args) {
    
           Singleton singleton=Singleton.getInstance();
    
           System.out.println("counter1:"+singleton.getCounter1());
    
           System.out.println("counter2:"+singleton.getCounter2());
    
           System.out.println(singleton.getClass().getClassLoader());
    
        }
    
    }

    我们调用Singleton singleton=Singleton.getInstance();调用Singleton的静态方法,相当于主动使用了类Singleton,因此Singleton被初始化!
    当我们看到显示的是:

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

    这样的时候,顺序执行,先赋予初始值,singleton为null,counter1为0,counter2为0,然后顺序对singleton赋予正确的值newSingleton(),执行构造函数,counter1增加变为1,然后counter2变为1,然后继续执行初始化,将counter2赋值为正确的值,将counter2修改为0,因此运行结果是1、0

    反过来

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

    先赋予初始值counter1为0,counter为0,singleton为null,然后对counter2赋值为正确的值,counter2为0,然后对singleton执行初始化赋予正确的值new Singleton(),执行构造函数,counter1为1,counter2为1,因此执行结果是1、1

    到此,是不是感觉前面的问题豁然开朗了呢?

    参考资料:

    圣思园张龙老师深入Java虚拟机系列

  • 相关阅读:
    Java 8 Lambda 表达式
    OSGi 系列(十二)之 Http Service
    OSGi 系列(十三)之 Configuration Admin Service
    OSGi 系列(十四)之 Event Admin Service
    OSGi 系列(十六)之 JDBC Service
    OSGi 系列(十)之 Blueprint
    OSGi 系列(七)之服务的监听、跟踪、声明等
    OSGi 系列(六)之服务的使用
    OSGi 系列(三)之 bundle 事件监听
    OSGi 系列(三)之 bundle 详解
  • 原文地址:https://www.cnblogs.com/pony1223/p/8641181.html
Copyright © 2020-2023  润新知