1、类的加载过程、类的加载器、双亲委派机制、类的主动使用和被动使用


在这里插入图片描述

类的加载过程和类的加载器

在这里插入图片描述

类的加载过程

一、加载

  • 通过一个类的全限定类名获取定义此类的二进制字节流;
  • 将这个字节流所代表的静态存储结构,转换为方法区的运行时数据结构;
  • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

二、链接

1、验证

主要包括四种验证:文件格式验证,元数据验证,字节码验证,符号引用验证。

Class文件的起始字节码:CAFE BABE。

在IDEA的设置中安装jclasslib插件,用于查看Class字节码的内容。

2、准备

  • 为类变量(static变量)分配内存,并且设置该类变量的默认值,即零值;
  • 这里不包含用final修饰的类变量,准备阶段final常量显式初始化;
  • 这里不会为实例变量分配内存,类变量会分配在方法区中,而实例变量是随着对象创建分配在Java堆中

3、解析

  • 将Class文件中常量池Constant pool内的符号引用转换为直接引用;
  • 符号引用:一组符号来描述所引用的目标;
  • 直接引用:直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄;
  • 解析动作主要针对类/接口、字段、类方法、接口方法、方法类型等。对应常量池中的CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等。

三、初始化

  • 初始化阶段就是执行类构造器方法<clinit>()的过程;
  • 此方法无需定义,是javac编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并而来
  • 构造器方法中指令按语句在源文件中出现的顺序执行;
  • <clinit>()不同于类的构造器;
  • 若该类具有父类,JVM会保证子类的<clinit>()执行前,父类的<clinit>()已经执行完毕;
  • 虚拟机必须保证一个类的<clinit>()方法在多线程下被同步加锁。
package org.westos.demo;

/**
 * @author lwj
 * @date 2020/5/21 17:20
 */
public class MyTest {
    private static int NUM = 1;

    static {
        NUM = 2;
        NUMBER = 20;
        //System.out.println(NUMBER); 非法前向调用,可以赋值,但是不可以访问未定义的类变量
    }

    private static int NUMBER = 10;
    //在链接的准备阶段,已经为NUMBER类变量分配内存,初始化零值
    //在初始化阶段,按照代码顺序执行<clinit>()

    public static void main(String[] args) {
        System.out.println(MyTest.NUM);
        //2
        System.out.println(MyTest.NUMBER);
        //10
    }
}

在这里插入图片描述

所以最终NUMBER类变量的值为10。

任何一个类声明以后,都会默认存在一个无参的构造器,对应Methods下的<init>()方法。

类加载器

package org.westos.demo;

/**
 * @author lwj
 * @date 2020/5/21 20:10
 */
public class MyTest2 {
    public static void main(String[] args) {
        //获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);
        //sun.misc.Launcher$AppClassLoader@18b4aac2
        //内部类

        //获取系统类加载器的父类:扩展类加载器
        ClassLoader parent = systemClassLoader.getParent();
        System.out.println(parent);
        //sun.misc.Launcher$ExtClassLoader@1b6d3586


        //试图获取扩展类加载器的父类:引导类加载器
        ClassLoader parentParent = parent.getParent();
        System.out.println(parentParent);
        //null
        //获取不到引导类加载器

        //加载用户自定义类使用系统类加载器
        ClassLoader classLoader = MyTest.class.getClassLoader();
        System.out.println(classLoader);
        //sun.misc.Launcher$AppClassLoader@18b4aac2


        //加载Java核心API使用引导类加载器
        ClassLoader classLoader1 = String.class.getClassLoader();
        System.out.println(classLoader1);
        //null
        ClassLoader classLoader2 = StringBuffer.class.getClassLoader();
        System.out.println(classLoader2);
        //null
        //Java的核心类库都是由引导类加载器加载的        
    }
}

一、类加载器的分类

启动类加载器(引导类加载器,Bootstrap ClassLoader)

  • 这个类加载器使用C/C++语言实现,嵌套在JVM内部
  • 用于加载Java的核心库(JAVA_HOME/jre/lib/rt.jar、resource.jar或者sun.boot.class.path路径下的内容),用于提供JVM自身所需要的类
  • 并不继承自java.lang.ClassLoader,没有父加载器
  • 加载扩展类和应用程序类加载器,并指定为他们的父类加载器
  • 出于安全考虑,启动类加载器只加载包名为java、javax、sun等开头的类

扩展类加载器(Extension ClassLoader)

  • Java语言编写,由sun.misc.Launcher$ExtClassLoader实现
  • 派生于ClassLoader类
  • 父类加载器为启动类加载器
  • 从java.ext.dir系统属性所指定的目录中加载类库,或者从JAVA_HOME/jre/lib/ext目录下加载类库

应用程序类加载器(系统类加载器,AppClassLoader)

  • Java语言编写,由sun.misc.Launcher$AppClassLoader实现
  • 派生于ClassLoader类
  • 父类加载器为扩展类加载器
  • 负责加载环境变量classpath下的类库
  • 通过ClassLoader.getSystemClassLoader()可以获取到该类加载器

二、双亲委派机制

在这里插入图片描述

工作原理:

  • 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类加载器去执行
  • 如果父类加载器还存在其父类加载器,则进一步向上委托,直到请求最终到达顶层的启动类加载器;
  • 如果父类加载器可以完成加载任务,就成功返回;如果父类加载器无法完成此加载任务,子类加载器才会尝试自己去加载。

这就是双亲委派模型。

package java.lang;

/**
 * @author lwj
 * @date 2020/5/22 13:42
 */
public class String {
    /*
    在src目录下创建java.lang.String类
     */

    static {
        System.out.println("java.lang.String的静态代码块");
    }
}
package org.westos.demo;

/**
 * 3、双亲委派机制
 * @author lwj
 * @date 2020/5/21 20:47
 */
public class MyTest3 {
    public static void main(String[] args) {
        String s = new String();
        //这里的String还是Java API中的java.lang.String
        System.out.println(s);
        //并不会执行自定义String中的静态代码块,因为启动类加载器加载的是Java核心类库下的String
    }
}

三、双亲委派机制的优势

package java.lang;

/**
 * @author lwj
 * @date 2020/5/23 17:06
 */
public class MyTest {
    public static void main(String[] args) {
        System.out.println("测试类");
    }
}
package org.westos.demo;

/**
 * @author lwj
 * @date 2020/5/23 17:07
 */
public class MyTest4 {
    public static void main(String[] args) {
        java.lang.MyTest myTest = new java.lang.MyTest();
        //启动类加载器没有找到这个类
    }
}

抛出异常:禁止的包名java.lang

在这里插入图片描述

package java.lang;

/**
 * @author lwj
 * @date 2020/5/22 13:42
 */
public class String {
    /*
    在src目录下创建java.lang.String类
     */

    static {
        System.out.println("java.lang.String的静态代码块");
    }

    public static void main(String[] args) {
        System.out.println("main...");
    }

    /*
    错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为:
       public static void main(String[] args)
    否则 JavaFX 应用程序类必须扩展javafx.application.Application

    Process finished with exit code 1
     */
}

四、类的主动使用和被动使用

在JVM中表示两个Class对象是否为同一个类的两个必要条件:

  • 类的完整类名必须一致,包括包名;
  • 加载这个类的ClassLoader(指ClassLoader实例对象)必须相同。

类的主动引用(一定会发生类的初始化)

  • 虚拟机启动,会先初始化main方法所在的类;(执行main方法所在类的静态代码块)
package org.westos.demo4;

/**
 * @author lwj
 * @date 2020/4/16 19:37
 */
public class MyTest2 {
    static int m = 20;
	
    static {
        m = 30;
        System.out.println("main方法所在的类,虚拟机启动,会执行初始化");
        //main方法所在的类,虚拟机启动,会执行初始化
        System.out.println("m = " + m);
        //m = 30
    }

    public static void main(String[] args) {
		
    }
}
  • new一个类的对象
package org.westos.demo4;

/**
 * @author lwj
 * @date 2020/4/18 11:33
 */
public class MyTest3 {
    public static void main(String[] args) {
        Student student = new Student();
    }
}

class Student {
    static int num = 20;

    static {
        System.out.println("num = " + num);
        //num = 20
        System.out.println("Student的静态代码块");
        //Student的静态代码块
    }

    public Student() {

    }
}
  • 调用类的静态成员(除了final static常量)和静态方法
package org.westos.demo4;

/**
 * @author lwj
 * @date 2020/4/18 11:37
 */
public class MyTest4 {
    public static void main(String[] args) {
        //System.out.println(Demo.num);
        //20
        //Demo.show();
    }
}

class Demo {
    static int num = 20;

    static {
        System.out.println("调用类的静态变量和静态方法,会触发类的初始化");
        //调用类的静态变量和静态方法,会触发类的初始化  Demo.num
        //调用类的静态变量和静态方法,会触发类的初始化  Demo.show()
    }

    public static void show() {
        System.out.println("num = " + num);
        //num = 20
    }
}
  • 使用java.lang.reflect包的方法对类进行反射调用
package org.westos.demo4;

/**
 * @author lwj
 * @date 2020/4/18 11:43
 */
public class MyTest6 {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> clazz = Class.forName("org.westos.demo4.Person");
    }
}

class Person {
    static int num = 30;

    static {
        System.out.println("使用java.lang.reflect包对类进行反射操作会触发类的初始化");
        //使用java.lang.reflect包对类进行反射操作会触发类的初始化
    }
}
  • 当初始化一个类时,如果其父类没有被初始化,则会先初始化它的父类
package org.westos.demo4;

/**
 * @author lwj
 * @date 2020/4/18 11:45
 */
public class MyTest7 {
    public static void main(String[] args) {
        SubClass subClass = new SubClass();
        //当初始化一个类时,如果其父类还没有初始化,那么应该先初始化其父类
    }
}

class SuperClass {
    static {
        System.out.println("父类初始化");
        //1
    }
}

class SubClass extends SuperClass {
    static {
        System.out.println("子类初始化");
        //2
    }
}

类的被动引用(不会触发类的初始化)

  • 当访问一个静态域时,只有真正声明这个域的类才会被初始化
    • 当通过子类引用访问父类静态变量,不会导致子类的初始化
package org.westos.demo4;

/**
 * @author lwj
 * @date 2020/4/18 11:47
 */
public class MyTest8 {
    public static void main(String[] args) {
        System.out.println(Son.num);
        //通过子类引用父类的静态变量,只会初始化父类
        //20
    }
}

class Father {
    static int num = 20;

    static {
        System.out.println("父类的静态代码块执行了");
        //1
    }
}

class Son extends Father {

    static {
        System.out.println("子类的静态代码块执行了");
    }
}
  • 通过数组定义类引用,不会触发类的初始化
package org.westos.demo4;

/**
 * @author lwj
 * @date 2020/4/18 11:53
 */
public class MyTest9 {
    public static void main(String[] args) {
        Teacher[] teachers = new Teacher[3];
        //通过数组定义类引用,不会触发类的初始化
    }
}

class Teacher {
    static {
        System.out.println("Teacher类的静态代码块执行了");
    }
}
  • 引用常量不会触发类的初始化(常量在链接的准备阶段已经完成了赋值)
package org.westos.demo4;

/**
 * @author lwj
 * @date 2020/4/18 11:41
 */
public class MyTest5 {
    public static void main(String[] args) {
        System.out.println(Demo2.NUMBER);
        //20
        //不会输出静态代码块的内容
    }
}

class Demo2 {
    static final int NUMBER = 20;

    static {
        System.out.println("调用static final修饰的常量,不会触发类的初始化,因为常量在链接阶段已经赋值完成了");
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值