• 类加载机制与对象初始化


    一 . 类加载机制

    • 类加载机制是指.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>libext目录下的,或者被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中普通代码块,构造代码块,静态代码块区别及代码示例

  • 相关阅读:
    mysql分表和表分区详解
    CNN 文本分类
    基于深度学习的目标检测研究进展
    标注工具
    在Ubuntu登陆界面输入密码之后,黑屏一闪后,又跳转到登录界面
    R-CNN,SPP-NET, Fast-R-CNN,Faster-R-CNN, YOLO, SSD系列深度学习检测方法梳理
    LeNet,AlexNet,GoogleLeNet,VggNet等网络对比
    nginx使用与配置入门指南
    CentOS 7上重新编译安装nginx
    酸汤肉沫豆腐
  • 原文地址:https://www.cnblogs.com/pepper7/p/6959605.html
Copyright © 2020-2023  润新知