类加载机制与对象初始化

一 . 类加载机制

  • 类加载机制是指.class文件加载到jvm并形成Class对象的机制。之后应用可对Class对象进行实例化并调用。类加载机制可在运行时动态加载外部的类,还可以达到类隔离的效果。

  • 类从而加载到虚拟机中开始,整个过程分为下图七个阶段,其中验证,准备,解析统称为解析。图中加载,验证,准备,初始化,卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种过程按部就班的开始,而解析则不一定,它在某些情况下可能在初始化之后才开始,这是为了支持java语言的运行时绑定。

     

    1 . 装载: 装载过程负责找到二进制字节码并加载至JVM--即负责查找和导入class文件。

    2 . 链接:

    (1)  检验:链接过程负责对二进制字节码的格式进行检验,检查class文件数据的正确性
    (2)  准备:初始化装载类中的静态变量,给类的静态变量分配存储空间
    (3)  解析:解析类中调用的接口,类,将符号引用转为直接引用。

    3 . 初始化:初始化过程即执行类中的静态初始化代码,构造器代码以及静态属性的初始化。以下四种情况会立即触发初始化          

             (1) 调用了new,读取或设置一个类的静态字段的时候,或者调用一个类的静态方法的时候。
             (2) 反射调用了类中的方法
    (3) 子类调用了初始化,如果子类初始化的时候发现父类尚未初始化,则会先出发父类的初始化。
    (4) JVM启动过程中指定的初始化类(包含main方法的类)
    (5)当使用JDK 1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄,并且这个方法句柄对应的类没有初始化,则需要先触发其初始化.

  • JVM 的类加载通过ClassLoader及其子类来完成,分别是Bootstrap ClassLoader,Extension ClassLoader,App ClassLoader 以及用户自定义的继承自ClassLoader抽象类的实现Custom ClassLoader。

  • (1) Bootstrap ClassLoader : 此类并非ClassLoader的子类,在代码中没办法拿到这个类的对象,Sun JDK会在启动时自动加载此类。此加载器会将存放于<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar 名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中

    (2) Extension ClassLoader : 此加载器会将<JAVA_HOME>\lib\ext目录下的,或者被java.ext.dirs系统变量所指定的路径中的所有类库加载.

    (3) App ClassLoader : 此加载器会将用户类路径(ClassPath)上所指定的类库加载。

  • (4) Custom ClassLoader : 基于自定义的ClassLoader加载非classpath中的jar及目录,还可以在加载之前对class文件做一些动作,例如加密。

  • JVM 的ClassLoader采用的是树形结构,除 Bootstrap ClassLoader外都会有父级的ClassLoader。加载类时通常会按照树形结构的原则进行。如果一个类加载器接收到了类加载的请求,它首先把这个请求委托给他的父类加载器去完成,每个层次的类加载器都是如此,因此所有的加载请求都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它在搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。 这样做的好处是:java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar中,无论哪个类加载器要加载这个类,最终都会委派给启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果用户自己写了一个名为java.lang.Object的类,并放在程序的Classpath中,那系统中将会出现多个不同的Object类,java类型体系中最基础的行为也无法保证,应用程序也会变得一片混乱。


二 . 初始化

java会尽量保证变量在使用前得到恰当的初始化,对于局部变量,如果未初始化会得到编译时的错误,对于成员变量会自动初始化(0,null等)。
可以使用构造方法初始化成员变量,在运行时刻,可以调用方法或者执行某些动作来确定初值。但需要记住一点:无法阻止自动初始化的进行,它在构造方法被调用之前发生。对于所有基本类型和对象引用,包括在定义时已经指定初始值的变量,情况也是一样的.例如如下实例代码,i首先会被置为0,然后替换为指定的初始值7.

public class TestClass(){
    int i;//自动初始化赋值为0
    public static void main(String[] args){
        i = 7;//赋值为7
    }
}

 

1 . 初始化的顺序:在类的内部,变量定义的先后顺序决定了初始化的顺序。即使变量的定义散布于方法定义之间,他们仍会在任何方法(包括构造方法)被调用之前得到初始化
对于如下代码:w3这个引用会被初始化两次,一次在调用构造方法之前,一次在调用期间,这时第一次引用的对象将被丢弃并被垃圾回收.

 public class House {
    Window w1 = new Window(1);
    public House() {
        System.out.println("House()");
        w3 = new Window(3_3);
    }
    Window w2 = new Window(2);
    public void method() {
        System.out.println("method()");
    }
    Window w3 = new Window(3);
    public static void main(String[] args) {
        House house = new House();
    }
}
class Window {
    Window(int order) {
        System.out.println("window" + order);
    }
} 
  //输出结果为:
  window1
  window2
  window3
  House()
  window33

 

2 . 静态数据的初始化
静态初始化只有在必要的时候才进行,初始化的顺序是先静态对象(如果它们尚未因前面的的对象创建过程而被初始化),而后是“非静态”对象。
示例代码如下:

public class StaticInitailization {
    public static void main(String[] args) {
        System.out.println("creating new CupBoard() in main");
        new CupBoard();
        System.out.println("creating new CupBoard() in main");
        new CupBoard();
        table.f2(1);
        cupBoard.f3(1);
    }
    static Table table = new Table();
    static CupBoard cupBoard = new CupBoard();
}

class Bowl {
    public Bowl(int i) {
        System.out.println("Bowl(" + i + ")");
    }
    void f1(int i) {
        System.out.println("f1(" + i + ")");
    }
}

class Table {
    static Bowl bowl1 = new Bowl(1);
    Table() {
        System.out.println("Table()");
        bowl2.f1(1);
    }
    void f2(int i) {
        System.out.println("f2(" + i + ")");
    }
    static Bowl bowl2 = new Bowl(2);
}

class CupBoard {
    Bowl bowl3 = new Bowl(3);
    static Bowl bowl4 = new Bowl(4);
    CupBoard() {
        System.out.println("CupBoard()");
        bowl4.f1(2);
    }
    void f3(int i) {
        System.out.println("f3(" + i + ")");
    }
    static Bowl bowl5 = new Bowl(5);
}
// 结果如下:
Bowl(1)
Bowl(2)
Table()
f1(1)
Bowl(4)
Bowl(5)
Bowl(3)
CupBoard()
f1(2)
creating new CupBoard() in main
Bowl(3)
CupBoard()
f1(2)
creating new CupBoard() in main
Bowl(3)
CupBoard()
f1(2)
f2(1)
f3(1)

 

3 . 对象的创建过程: 假设有个名为Dog 的类

(1) 即使没有显示的使用static关键字,构造方法实际上也是静态方法。因此,当首次创建Dog类的对象时,或者Dog类的静态方法或者静态变量被访问时,java解释器必须先查找类路径,以定位Dog.class文件

(2)载入Dog.class文件(这将创建一个Class对象),有关静态初始化的所有动作都会执行。因此,静态初始化只在类首次加载的时候进行一次

(3)用new Dog 创建对象的时候,首先会在堆上为该对象分配足够的存储空间

(4)这块存储空间会被清零,这将会自动的为该对象的的所有基本数据类型赋初值

(5)执行所有出现于字段定义处的初始化动作。

(6)执行构造方法。

  • 复杂对象调用构造方法的顺序:
    (1) 在其他任何事物发生之前,将分配给对象的的存储空间初始化为二进制的零
    (2) 调用父类的构造方法
    (3) 按照声明顺序调用成员的初始化方法
    (4) 调用子类构造方法的主体部分

  • 编写构方法注意避免调用其他方法,在构造方法内唯一能安全调用的方法是父类中的final方法(也适用于private方法,他们自动属于final方法)。这些方法不能被override(覆写)

参考书籍:分布式java应用基础与实践--林昊 3.1.2节 类加载机制
参考博客1:深入理解Java:类加载机制及反射
参考博客2:Java中普通代码块,构造代码块,静态代码块区别及代码示例

posted @ 2017-07-17 11:24  cello_ist  阅读(316)  评论(0编辑  收藏  举报