美文网首页
类加载机制的初始化

类加载机制的初始化

作者: 别拿爱情当饭吃 | 来源:发表于2018-12-20 15:39 被阅读7次

    类初始化阶段是类加载过程的最后一步,这一步操作是由虚拟机主导和控制的。

    你看《深入理解java虚拟机》书的时候,会看到这样子的一个东西:<clinit()>。

    <clinit()>是什么?

    <clinit()>方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,收集的顺序由源文件中出现的语句顺序决定的。
    提取关键知识点:<clinit()>就是由类变量和静态语句块组成的。

    拓展:静态语句块中能访问在静态语句块前定义的变量和对变量赋值;
    静态语句块中不能访问在静态语句块后定义的变量,但能对变量赋值。

    /**
     * 静态语句块只能访问到定义在静态语句块前的变量;
     * 定义在静态语句块后的变量,只能赋值,不能访问。
     * @author aaron
     *
     */
    public class Test {
        static {
            i = 0;
            //System.out.println(i);//定义在静态语句块后的变量,只能赋值,不能访问
        }
        static int i = 1;
        
        static int j = 2;
        static {
            j = 3;
            System.out.println(j);//定义在静态语句块前的变量,可以赋值,可以访问。
        }
    }
    
    

    不离题了,说回<clinit()>方法
    知识点1:<clinit()>方法与类的构造函数不一样,她不需要显示的调用父类的构造器,虚拟机会保证在执行子类的<clinit()>方法前,会先执行父类的<clinit()>方法。因此虚拟机中,执行第一个<clinit()>方法的类肯定是java.lang.Object。

    由于父类的<clinit()>方法先执行,这也解释了为什么父类中定义的静态语句块一定会优先于子类中定义的静态语句块。看下面的例子1:
    例子1:

    /**
     * output:
     * A=2
     * 2
     * @author aaron
     *
     */
    public class Parent {
        static int A = 1;
        static {
            A = 2;
            System.out.println("A="+A);
        }
        public static void main(String[] args) {
            System.out.println(Sub.B);
        }
    }
    class Sub extends Parent{
        public static int B = A;
    }
    
    

    继续谈<clinit()>方法:
    知识点2:<clinit()>方法对于类或接口不是必需的,如果这个类没有类变量,也没有静态语句块。
    那么编译器是不会为这个类生成<clinit()>方法的。

    知识点3:接口中虽然不能使用静态语句块,但可以有类变量,所以接口也可以有<clinit()>方法的。
    但是接口和类稍微有点不同,接口执行<clinit()>方法时,不需要先执行它的父接口的<clinit()>方法。只有当父接口中的定义的变量被使用时,才会执行<clinit()>方法。

    知识点4:虚拟机会保证一个类<clinit()>方法在多线程环境下被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit()>方法,其他线程都需要阻塞等待,直到线程执行<clinit()>完毕。(看例子2)
    例子2:

    /**
     * 有死循环的情况下:
     * output:
     * Thread[Thread-0,5,main]start
    Thread[Thread-1,5,main]start
    Thread[Thread-0,5,main]DeadLoopClass init!!!
    一个线程在阻塞等待,另一个线程在死循环模拟长时间操作
    
    没死循环的情况下:
    output:
    Thread[Thread-0,5,main]start
    Thread[Thread-1,5,main]start
    Thread[Thread-0,5,main]DeadLoopClass init!!!
    Thread[Thread-0,5,main]over
    Thread[Thread-1,5,main]over
    虚拟机会保证一个类的<clinit()>方法在多线程环境中被正确地加锁,同步,如果多个线程同时去初始化一个类,
    那么只会有一个类能执行<clinit()>方法,其他线程都需要阻塞等待,直到活动线程执行<clinit()>方法完毕,
    才会继续后续操作。
     * @author aaron
     *
     */
    public class TestDeadLoopClass {
        public static void main(String[] args) {
            Runnable script = new Runnable() {
                
                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    System.out.println(Thread.currentThread()+"start");
                    DeadLoopClass deadLoopClass = new DeadLoopClass();
                    System.out.println(Thread.currentThread()+"over");
                }
            };
            Thread thread1 = new Thread(script);
            Thread thread2 = new Thread(script);
            thread1.start();
            thread2.start();
        }
    }
    class DeadLoopClass{
        static {
            if(true) {
                
                System.out.println(Thread.currentThread()+"DeadLoopClass init!!!");
                while(true){
                    
                }
            }
        }
    }
    
    

    相关文章

      网友评论

          本文标题:类加载机制的初始化

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