• 笔记:Java面向对象编程 第10章 类的生命周期


    一、JVM 的生命周期

    JVM 结束的时机:

    程序结束;程序因为异常或错误终止;System.exit();操作系统终止JVM


    二、类生命周期的开始

    步骤:

    (1)、加载 

    (2)、连接:包括验证(即确保其正确性)、准备(即为静态变量分配内存、初始化默认值)、解析(将符号引用转换为直接引用)

    (3)、初始化:为静态变量赋予初始值


    2.1  类的加载

    将class文件的二进制数据读入方法区,在堆区创建一个Class对象。

    注意:Class的数据结构在方法区,Class对象在堆区


    类加载器在加载过程中,如果遇到class缺失或损坏,不会马上报错而是等到首次主动使用该class时候才报错,如果一直不使用该class就不报错。


    2.2 类的验证

    验证的内容:检查结构、语义、字节码、二进制兼容


    2.3 类的准备

    为静态变量分配内存,赋予初值。例子:

    private static int a=1;
    private static long b;
    int c=3;
    
    static
    {
        b=3;
    }


    此时为a分配4个字节的内存空间,赋默认值0,为b分配8个字节空间,赋默认值0;而c是实例变量,此时不会分配空间

    2.4、类的解析

    符号引用解析为直接引用


    2.5、类的初始化

    在静态变量声明处或者静态代码块中初始化。

    注意:(1)、初始化一个类的时候,会先初始化其父类,但不会初始化其接口。初始化一个接口,不会初始化其父接口。

    (2)、声明时候不会初始化,使用到的时候才初始化

    MyObject  obj;   // 不会初始化MyObject
    obj  = new MyObject :  // 此时才初始化MyObject

    2.6、类初始化的时机


    主动使用的时候初始化:访问静态变量;访问静态方法;创建类的实例(包括new语句、反射、clone、反序列化);调用反射方法;初始化子类;JVM启动类。


    不会初始化的情况:

    (1)、使用编译时常量不会初始化

    对于final static 变量,如果编译时候就能算出其值,则为编译时常量。

    public final static int a=2*3+4;                 //编译时常量,首次使用不会导致类的初始化
    public final static int b=(int)Math.random();    //不是编译时时常量,首次使用会导致类的初始化


    (2)、接口:使用类不会初始化其实现的接口,使用子接口不会初始化其父接口


    interface IAction {}
    
    class Action implements IAction {}
    
    interface IAction2 extends IAction{}


    调用 Action 、IAction2 的静态变量、静态方法都不会初始化 IAction


    (3)、程序访问的静态变量或静态方法的确在本类时候,才会初始化,如果在父类,则只初始化父类

    class Father
    {
          public static int a=9;
          public static int hehe() {}
    }
    
    class Son extents Father
    {
    }


    如果调用 Son.a 和 Son.hehe(), 不会初始化Son类,只会初始化Father 类


    (4)、ClassLoader.loadClass() 是加载,不会初始化;Class.forName() 是初始化

    Class<?>  clazz = myClassLoader.loadClass("className")   //不会初始化,

    Class<?>  clazz = Class.forName("className")    // 会初始化



    三、类加载器

    1、父亲委托机制

    类加载器classLorderA加载类 时候,会先委托父加载器classLorderB去加载,父加载器classLorderB 又去委托其父加载器classLorderC 。。。直到根加载器,根加载器一层一层往下选,找到某个能加载的classLoader,则加载之。

    加载器委托关系:



    Bootstrap加载器  null:(加载JDK核心类库)

    Extension加载器 sun.misc.Launcher$ExtClassLoader:(加载 lib/ext/ 下类库)

    System 加载器 sun.misc.Launcher$AppClassLoader:(加载classpath中其他类库)



    注意:加载器之间的父子关系是包装关系,而不是继承关系,代码如下:

    ClassLoader fatherLoader = new MyClassLoader();
    ClassLoader sonrLoader = new ClassLoader(fatherLoader);
    


    父亲委托机制的好处:

    (1)、防止不可靠的代码替换由父加载器加载的可靠代码,例子:java.lang.Object 总是由Bootstrap加载器加载,用户定义的加载器不能加载 java.lang.Object  类来进行不安全的操作。

    (2)、同一个类文件,由两个不同的类加载器分别加载为实例classA、classB,此时classA 和 classB 仍然被看做不同的class,因为其类加载器不一样。

    (3)、假如用户定义一个类java.lang.MyObject,企图用java.lang,MyObject 去访问JDK核心包java.lang.*下面的包可见的成员(类、变量、方法),这是不可能成功的。因为核心类java.lang.* 由Bootstrap加载器加载,而java.lang.MyObject 由System加载器或者用户自定义加载器加载,加载器不同则属于不同的运行时包,只有属于同一运行时包的成员才可以相互访问包可见成员。

    2、创建自定义加载器

    继承 ClassLoader 类,覆盖 findClass(String name) 方法。

    类加载器之间的委托关系是包装关系,而不是继承关系,代码如下:

    ClassLoader fatherLoader = new MyClassLoader();
    ClassLoader sonrLoader = new ClassLoader(fatherLoader);


    多个加载器之间的命名空间关系如下:

    (1)、同一命名空间之间的类相互可见

    (2)、子加载器的加载的类可以看到父加载器的加载的类,父加载器的加载的类不能看到子加载器的加载的类

    (3)、若两个类加载器之间没有直接或间接的父子关系,则它们各自加载的类相互不可见

    (4)、两个不同命名空间的类相互不可见的时候,可以通过反射访问对方实例的属性和方法。


    3、java.net.URLClassLoader 

    java.net.URLClassLoader 属于用户加载器,而不是System加载器,虽然其包含在核心包中,通过URL从本地或远程加载类


    四、类的卸载

    Bootstrap、Extension、System加载器所加载的类,永远不会被卸载,因为JVM始终引用这些类加载器,而这些类加载器又始终引用其加载的Class对象

    用户自定义加载器所加载的类,在Class对象不再被引用时,会被卸载。注意:

    (1)、一个类的实例总是引用代表这个类的Class对象,如instance.getClass() 总能返回其Class 对象。故要卸载Class,其所有的instance 必须也不再被引用。

    (2)、类加载内部有一个集合保全了其所加载的所有Class的引用,故要卸载Class,其类加载器也必须被卸载。

    (3)、以下三个变量c1、c2、c3是同一个Class 对象

    Class<?>  c1 = mypackage.MyClass.class;
    Class<?>  c2 = (new mypackage.MyClass()).getClass();
    Class<?>  c3 = Class.forName("mypackage.MyClass");

    Class<?> c4 = myClassLoader,loadClass("mypackage.MyClass") 


    则不一定与c1、c2、c3 相同,因为可能不是同一个类加载器








  • 相关阅读:
    冷门JS技巧
    JavaScript小技巧整理篇(非常全)
    JS实现标签页切换效果
    MySQL主从配置详解
    mysql主从复制(超简单)
    1.4isAlive()方法
    1.3currentThread()方法
    1.2.4注意Sysyem.out.println与i--
    1.2.3实例变量与线程安全
    1.2.2实现Runnable接口
  • 原文地址:https://www.cnblogs.com/leeeee/p/7276183.html
Copyright © 2020-2023  润新知