虚拟机加载机制

来源:互联网 发布:大数据平台调度系统 编辑:程序博客网 时间:2024/06/18 08:54
java中的类并不是在运行或编译时就被初始化的,而是在运行的过程中需要的时候才被初始化,一个类的生命周期包括下面7个阶段:
加载->验证->准备->解析->初始化->使用->卸载
其中,加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,“解析”可能在初始化之前或之后进行,“使用”应该也能在初始化之前或之后进行。

类何时被初始化
一个类当且仅当在下面4种情况之一发生时才会被初始化:
1、遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,最常见的java代码场景是:使用new实例化对象时、读取或设置一个类的静态字段(被final修饰、在编译期已经放入常量池的静态字段除外,也就是说,读取或设置该字段不会导致类的初始化,所以使用可能才初始化之前)时,以及调用一个类的静态方法时。
2、虚拟机启动时,用户需要指定一个包含main函数的主类,这个主类会先被初始化
3、初始化一个类时,如果发现其父类还没初始化,则先初始化其父类
4、使用java.lang.reflect包的方法对类进行反射调用时
例子:
1、static块或static变量(除了final修饰以外)不会在程序启动就执行的,它们要等到类初始化时才被执行:

class A{

   static{

      System.out.println("I am A!");

   }

}

publicclass test1 {

    publicstaticvoid main(String[]args){}

}

没有任何输出,A类没有被初始化
2、

class SuperClass{

   static{

      System.out.println("SuperClass init!");

   }

}

class SubClass extends SuperClass{

   static{

      System.out.println("SubClass init!");

   }

}

publicclass test1 {

    publicstaticvoid main(String[]args){

       SubClass subClass = new SubClass();

    }

}

输出:
SuperClass init!
SubClass init!
两个类都被初始化了
3、

class SuperClass{

   static{

      System.out.println("SuperClass init!");

   }

}

class SubClass extends SuperClass{

   static{

      System.out.println("SubClass init!");

   }

}

publicclass test1 {

    publicstaticvoid main(String[]args){

       System.out.println(SubClass.class);

    }

}

输出:class SubClass
两个类都没有被初始化
4、

class SuperClass{

   static{

      System.out.println("SuperClass init!");

   }

   public static int value = 10;//该变量会放入方法区

}

class SubClass extends SuperClass{

   static{

      System.out.println("SubClass init!");

   }

}

publicclass test1 {

    public static void main(String[]args){

       System.out.println(SubClass.value);

    }

}

输出:
SuperClass init!
10
SuperClass被初始化,SubClass没有被初始化
5、

class SuperClass{

   static{

      System.out.println("SuperClass init!");

   }

   publicstaticfinalintfinalValue = 20;//该变量会放入方法区中的常量池里

}

class SubClass extends SuperClass{

   static{

      System.out.println("SubClass init!");

   }

}

publicclass test1 {

    publicstaticvoid main(String[]args){

       System.out.println(SubClass.finalValue);

    }

}

输出:20
两个类都没有被初始化,因为finalValue这个变量在程序启动时就已经放到常量池中,以后直接从常量池中取出便可
6、

class SuperClass{

   static{

      System.out.println("SuperClass init!");

   }

}

class SubClass extends SuperClass{

   static{

      System.out.println("SubClass init!");

   }

}

publicclass test1 {

    publicstaticvoid main(String[]args){

       SuperClass[]sup = new SuperClass[5];

       SubClass[]sub = new SubClass[5];

    }

}

没有任何输出,直到执行sup[i] = new SuperClass()时SuperClass才被初始化,静态代码块只执行一次,也就是说,后面再加上下面语句:
       sup[0] = new SuperClass();
       sub[0] = new SubClass();
打印结果为:
SuperClass init!
SubClass init!

注意:
Java中,创建元素类型为类类型的数组不会导致类的构造函数被调用,也不会导致类的初始化,但C++会导致构造函数被调用:

#include<iostream>

usingnamespace std;

classA

{

public:

    A()

    {

        cout << "A构造函数被调用" << endl;

    }

};

void main()

{

    A a[3];

}

输出:
A构造函数被调用

A构造函数被调用

A构造函数被调用

内部类不会因外部类初始化而被初始化

publicclass test1 {

   static{

      System.out.println("The main class is inited!");

   }

   staticclass innerClass{

      static{

         System.out.println("The innerClass is inited!");

      }

   }

    publicstaticvoid main(String[]args){

    }

}

输出:The main class is inited!
可见innerClass不会被初始化

publicclass test1 {

   staticclass innerClass{

      staticintA = 0;

      static{

         System.out.println("The innerClass is inited!");

      }

   }

    publicstaticvoid main(String[]args){

       System.out.println(innerClass.A);

    }

}

输出:
The innerClass is inited!
0

publicclass test1 {

   staticclass Parent{

      publicstaticintA = 0;

      static{

         System.out.println("Parent is inited!");

      }

   }

   staticclass Child extends Parent{

      static{

         System.out.println("Child is inited!");

      }

   }

    publicstaticvoid main(String[]args){

       System.out.println(Child.A);

    }

}

输出:
Parent is inited!
0
可见内部类的初始化也要满足前面提到的初始化4个条件

publicclass test1 {

   staticclass Parent{

      publicstaticintA = 0;

      static{

         A = 2;

      }

   }

   staticclass Child extends Parent{

      publicstaticintB = A;

   }

    publicstaticvoid main(String[]args){

       System.out.println(Child.B);

    }

}

输出:2

初始化执行顺序
类初始化是类加载过程的最后一步,类初始化时会执行静态变量初始化、静态代码块。
注意:构造函数不一定被调用!!因为构造函数需要显式调用的。
总的执行顺序:
总是先执行静态代码部分再执行构造函数(如果会执行的话),最后才是普通代码块:
例子:

publicclass Test{

   public Test(){

      System.out.println("构造函数");

   }

   static{

      System.out.println("static");

   }

   publicstaticvoid main(String []args){

      Test test = new Test();

   }

}

输出:
static块
构造函数
public class Person {
    static{
        System.out.println("person static");
    }
    public Person(){
        System.out.println("person structor");
    }
public class Test extends Person{
    static{
        System.out.println("test static");
    }
    public Test(){
        System.out.println("test constructor");
    }
    public static void main(String[] args) {}
}
输出:
person static
test static
上面例子如果在main函数中加上下面语句:
new Test();
则输出:
person static
test static
person structor
test constructor


静态部分的顺序:
按照代码的顺序执行,前面的不能访问后面声明的静态变量,但可以对后面声明的静态变量赋值(普通方法对后面的非静态变量既不能访问也不能赋值)
例子:

publicclass Test{

   static{

      n = 2;//如果是System.out.println(n);则报错,因为n还没定义

   }

   publicstaticintn = 1;

   publicstaticvoid main(String []args){

      System.out.println(Test.n);

   }

}

输出1,如果两个静态部分位置交换,则输出2

易错
public class Test2 {
    private String baseName = "base";
    public Test2() {
        callName();
    }
    public void callName() {
        System.out.println(baseName);
    }
    static class Sub extends Test2 {
        private String baseName = "sub";
        public void callName() {
            System.out.println(baseName);
        }
    }
    public static void main(String[] args) {
        new Sub();
    }
}
上面代码输出:null
new Sub();在创造派生类的过程中首先创建基类对象,然后才能创建派生类。创建基类即默认调用Test2()方法,在方法中调用callName()方法,由于派生类中存在此方法,则被调用的callName()方法是派生类中的方法,此时派生类还未构造,所以变量baseName的值为null。如果把Sub类中的callName方法删掉,则输出base。
JVM会保证一个类的初始化被加锁
如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,<clinit>()方法指的是全部的static成员及static块,其它线程都需要阻塞等待,所以,如果一个类的初始化耗时比较长的话,会影响到别的线程的执行。
public class Person {
    static {
        System.out.println(Thread.currentThread().getId()+".....");
    }
    static {
        System.out.println(Thread.currentThread().getId()+".....");
    }
    static {
        System.out.println(Thread.currentThread().getId()+".....");
    }
    .......
public class Test {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                new Person();
            }
        };
        Thread thread1 =new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        thread1.start();
        thread2.start();
        System.out.println("main finish");
    }
}
输出:
main finish
1.....
1.....
1.....
......
有多个线程导致类的初始化,但只有一个线程真正使得类被初始化,其它线程一直等待直到类被初始化完成。
public class Person {
    static {
        System.out.println("static...");
        if(true){
            while(true){}
        }
    }
public class Test {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                new Person();
                System.out.println("thread finish");
            }
        };
        Thread thread1 =new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        thread1.start();
        thread2.start();
        System.out.println("main finish");
    }
}
输出:
main finish
static...
可见另一个线程也被阻塞。


类何时被卸载
这里说的是类的卸载,而不是类对象或实例的内存回收,java中采用的是双亲委派机制实现类的加载,同一个加载器对一个类只会加载一次,加载之后可以创建多个实例,如果希望对一个类加载多次,则需要实现自定义的加载器,然后破坏双亲委派机制。类的实例的回收也就是对象的回收,根据根搜索如果没找到一条与之连通路径,则便可标记为垃圾,便可以等待被回收,但类本身什么时候才可以被回收呢?首先判断一个类是否是“无用的类”要同时满足下面3个条件:
1、该类的所有实例都已经被回收;
2、加载该类的ClassLoader已经被回收,这里应该是说ClassLoader的实例,如果是ClassLoader被卸载,则加载该ClassLoader的ClassLoader也要被卸载,依次类推,直至jvm的系统类加载器,这样便无法回收类;
3、该类对应的java.lang.Class对象没有在任何地方被引用,也就是说无法在任何地方通过反射来访问该类;
满足上面3个条件仅仅说明虚拟机“可以”对该类进行回收,并不说明一定会被回收,具体什么时候回收与虚拟机的实现有关,通常为了让加载的类可以被卸载,需要自定义ClassLoader,所以很多使用反射、动态代理、CGLib等bytecode的框架以及OSGI都频繁使用自定义类加载器。





























0 0
原创粉丝点击