• jvm


    我们知道,我们写的java代码称为源码,想要能够被jvm执行首先需要编译成.class文件,那么编译完到使用又都经理的哪些阶段呢?主要分为以下三个阶段:

    • 加载:查找并加载类的二进制数据(.class文件硬盘到内存的一个过程)。
    • 连接
       - 验证:确保被加载的类的正确性。
       - 准备:为类的 静态变量分配内存,并将其初始化为默认值。
       - 解析:把类中的符号引用转换为直接引用。
    • 初始化:为类的静态变量赋予正确的初始值。

    实例(一)

    class Singleton1 {
        private static Singleton1 singleton = new Singleton1();
        public static int counter1;
        public static int counter2 = 0;
    
        public Singleton1() {
            counter1++;
            counter2++;
        }
    
        public static Singleton1 getInstance() {
            return singleton;
        }
    }
    
    class Singleton2 {
        public static int counter1;
        public static int counter2 = 0;
        private static Singleton2 singleton = new Singleton2();
    
        public Singleton2() {
            counter1++;
            counter2++;
        }
    
        public static Singleton2 getInstance() {
            return singleton;
        }
    }
    
    public class Test0010 {
        public static void main(String[] args) {
            Singleton1 singleton1 = Singleton1.getInstance();
            System.out.println(singleton1.counter1);
            System.out.println(singleton1.counter2);
            Singleton2 singleton2 = Singleton2.getInstance();
            System.out.println(singleton2.counter1);
            System.out.println(singleton2.counter2);
        }
    }

    结果:

    如果和你想的不一样的话,不要急,继续往下看:

    Java程序对类的使用方式可分为两种

    • 主动使用
    • 被动使用

    主动使用

    • 创建类的实例
    • 访问某个类或接口的静态变量,或者对该静态变量赋值
    • 调用类的静态方法
    • 反射(如Class.forName(“com.xxx.Test”))
    • 初始化一个类的子类
    • Java虚拟机启动时被标明为启动类的类(Java ClassA)

    除了主动使用外的其他方式都是被动使用。所有的Java虚拟机实现必须在每个类或接口被Java程序“首次主动使用”时才初始化他们。会过头来解析上面的例子:
      我们在使用一个类之前首先要经历:加载->链接->初始化的过程,而在链接阶段又有验证准备解析几个过程,重点是准备阶段:为类的 静态变量分配内存,并将其初始化为默认值。这里很重要,也就是我们在首次主动使用类之前,类中的静态变量已经被初始化为了默认值,而在真正的主动使用时才会将静态变量初始化为应有的初始值。来看类Singleton1,在链接阶段过后,类中静态成员的情况是这样子的:

    class Singleton1 {
        private static Singleton1 singleton = null;
        public static int counter1 = 0;
        public static int counter2 = 0;
    }

    在Test0010的main方法中,我们调用了Singleton1的如下方法和成员,这里是对Singleton1类的首次主动使用,会引发类的初始化。

    Singleton1 singleton1 = Singleton1.getInstance();
    System.out.println(singleton1.counter1);
    System.out.println(singleton1.counter2);

    初始化执行过程中主要执行类的构造方法,并且按照顺序执行里面的静态语句和静态代码块

    执行private static Singleton1 singleton = new Singleton1(); 时,counter1和counter2均自增,此时counter1和counter2的值均为1,继续往下执行时counter2 又被赋值为0,所以最终
    counter1和counter2的值分别为1和0。同样的道理不难看出Singleton2中初始化操作顺序执行静态语句之后,counter1和counter2的值分别为1和1。

    实例(二)

    class FinalTest1 {
        /**
         * 编译时常量,访问它不会初始化这个类
         */
        public static final int x = 100 / 2;
    
        static {
            System.out.println("final block ~~~");
        }
    }
    
    class FinalTest2 {
        /**
         * 运行时常量,访问它会初始化这个类
         */
        public static final int x = new Random().nextInt(100);
    
        static {
            System.out.println("final block ~~~");
        }
    }
    
    public class Test0020 {
        public static void main(String[] args) {
            System.out.println(FinalTest1.x);
            System.out.println(FinalTest2.x);
        }
    }

    我们看到,FinalTest1 中的静态代码块没有执行,而FinalTest2中的静态代码块执行了。
    结论:首次主动访问编译时常量,不会初始化这个类;而首次主动访问运行时常量,会初始化这个类。

    同时要注意:静态代码块在类初始化时候才会执行。

    实例(三)

    public class Test0030 {
        static {
            System.out.println("main static block");
        }
    
        public static void main(String[] args) {
            System.out.println(Child.b);
        }
    }
    
    class Parent {
        static {
            System.out.println("parent static block");
        }
    }
    
    class Child extends Parent {
        static int b = 4;
    
        static {
            System.out.println("child static block");
        }
    }

    结论: 当初始化一个类时,会先初始化这个类的父类。

    实例(四)

    public class Test0050 {
        public static void main(String[] args) {
            System.out.println(Child.a);
        }
    }
    
    class Parent {
        static int a = 3;
    
        static {
            System.out.println("parent static block");
        }
    }
    
    class Child extends Parent {
        static {
            System.out.println("child static block");
        }
    }

     结论:有当程序访问的静态变量或静态方法确实在当前类或接口中定义时,才可以认为是对类的主动使用。

    实例(五)

    public class Test0060 {
    
        public static void main(String[] args) throws ClassNotFoundException {
            ClassLoader classLoader = ClassLoader.getSystemClassLoader();
            Class clazz = classLoader.loadClass("com.chengli.jvm.classloader.v0060.CL");
            System.out.println("------------------------------");
            clazz = Class.forName("com.chengli.jvm.classloader.v0060.CL");
        }
    }
    
    class CL {
        static {
            System.out.println("class CL static block");
        }
    }

    结论:调用ClassLoader的loadClass方法加载一个类,并不是对类的主动使用

  • 相关阅读:
    Kafka API: TopicMetadata
    从事件总线和消息队列说起
    Object.defineproperty实现数据和视图的联动
    css3动画-animation
    css3动画-transition
    jquery判断对象的type
    vs如何在运行iis express调试时,不开打新窗口和关闭调试时,iis express不退出
    重写Equals的方式
    Android中包名不能大写
    C# 几种常见数据结构【转】
  • 原文地址:https://www.cnblogs.com/thiaoqueen/p/9067037.html
Copyright © 2020-2023  润新知