美文网首页
类加载连接初始化阶段

类加载连接初始化阶段

作者: shz_Minato | 来源:发表于2019-01-17 16:48 被阅读20次

    一、类的生命周期和加载阶段概述

    类的生命周期

    类的生命周期.png

    类的加载连接初始化过程

    类的加载连接初始化.png

    二、类的加载连接初始化详细过程

    加载
     使用类加载将二进制文件,装载入内存之中。
     类加载器分为两种:
      ①JVM自带的加载器
       根类加载器
       扩展类加载器
       系统或应用类加载
      ②用户自定义的类加载器
       特点:
       直接或间接继承自java.lang.ClassLoader
       用户可以定制类的加载方式
     类加载器并不需要等到某个类被"首次使用时"才加载它,下面实例验证

    //为JVM配置打印加载参数 -XX:+TraceClassLoading
    
    public class MyTest {
        public static void main(String[] args) {
            System.out.println(Son.a);
        }
    }
    
    class Parent{
        public static int a=3;
    
        static {
            System.out.println("I am parent");
        }
    }
    
    class Son extends Parent{
        static {
            System.out.println("I am son");
        }
    }
    
    //运行部分结果如下
    [Loaded com.minato.jvm.Parent from file:/D:/Java_Develop/Java_Workspace/JVMGuide/out/production/classes/]
    [Loaded com.minato.jvm.Son from file:/D:/Java_Develop/Java_Workspace/JVMGuide/out/production/classes/]
    
    I am parent
    3
    
    结果表明 Son类没有初始化,但是已经加载了。
    出现这种现象的原因:
        JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果预先加载的
        过程中遇到了class文件缺失或存在错误,类加载器必须在程序首次主动使用该类
        时才报告错误。
    
    初始化的七种情况:
        创建某个类的实例
        使用类的静态变量或者设置类的静态变量
        调用类的静态方法
        初始化某个类的子类,会先初始化其父类
        反射
        JVM的启动类
        Java7以后的动态语言支持
    

    连接
     在准备阶段会为静态变量赋予指定类型的默认值,这样会保证在执行初始化语句时不发生空指针异常。
     在解析阶段会将类、方法、接口的符号引用替换为直接引用。

    初始化
     初始化时机
     ① 初始化的时机就是上述的七种情况
     ② 当JVM初始化一个类时,要求它的所有父类都已经被初始化,但是这条规则不适用于接口:
      初始化一个类时,并不会先初始化它所实现的接口。
      初始化一个接口时,并不会先初始化它的父接口。
      因此,一个父接口并不会因为它的子接口或者实现类的初始化而初始化。只有当程序首次使用特定接口的静态变量时,才会导致该接口的初始化。
     初始化步骤
     ① 假如这个类还没有被加载和连接,就先进行加载和连接
     ② 假如这个类存在直接父类,并且这个父类还没有被初始化,那就先初始化直接父类
     ③ 假如类中存在初始化语句,那就依次执行初始化语句

    public class MyTest2 {
        public static void main(String[] args) {
            //第一处
            Singleton singleton=new Singleton();
            System.out.println(Singleton.counter1);
            System.out.println(Singleton.counter2);
        }
    }
    
    class Singleton {
        public static int counter1;
        public static int counter2 = 0;
    
    
    
        public Singleton() {
            counter1++;
            counter2++;//准备阶段的重要意义 可以有值可用
        }
    
    }
    // 执行结果 
      1
      1
      执行流程分析:
      第一:第一处时,是类的主动使用,因此会执行加载连接初始化流程。
        在准备阶段为 counter1和counter2赋予初值 0
        接着执行 初始化阶段 因为counter没有赋值,因此其值还是0,位counter2赋值0
        以上是类的加载连接初始化过程
        接着是类的使用阶段,执行构造方法 为counter1和counter2执行++操作。
        
        因此输出的结果是1和1
        
        如果源码发生变化如下
        
    public class MyTest2 {
        public static void main(String[] args) {
            //第一处
            Singleton singleton=Singleton.getSingleton();
            System.out.println(Singleton.counter1);
            System.out.println(Singleton.counter2);
        }
    }
    
    class Singleton {
        public static int counter1=1;
    
        private static Singleton singleton = new Singleton();
    
        private Singleton() {
            counter1++;
            counter2++;//准备阶段的重要意义 可以有值可用
        }
    
        public static int counter2 = 0;
    
        public static Singleton getSingleton() {
            return singleton;
        }
    
    // 执行结果 
      2
      0
      执行流程分析:
      第一:第一处时,是类的主动使用,因此会执行加载连接初始化流程。
        在准备阶段为 counter1、singleton和 counter2赋初值(0,null,0)。
        接着执行 初始化阶段 
        为counter1赋初值1,执行singleton赋值,执行构造方法。
            因为counter没有初始化,值任然是准备阶段的值0。 因此此时的两个的值是2,0
        为counter2初始化,赋值0
        以上是类的加载连接初始化过程
        
        
        因此输出的结果是2和0
        
     
    

    相关文章

      网友评论

          本文标题:类加载连接初始化阶段

          本文链接:https://www.haomeiwen.com/subject/htgidqtx.html