java 类加载机制(一) 类加载顺序以及双亲委派原则

看到一篇关于类加载机制的博客,觉得讲的很不错,顺带整理个笔记。文章末尾附链接。

类的加载顺序

先看一个例子:想一下这个会输出的结果是什么

public class ClassLoaderTest {

    public static ClassLoaderTest classLoaderTest = new ClassLoaderTest();
    public static int a;
    public static int b = 0;

    public static void main(String[] args) {
        ClassLoaderTest instance = getInstance();
        System.out.println(ClassLoaderTest.a);
        System.out.println(ClassLoaderTest.b);
    }
    public static ClassLoaderTest getInstance(){
        return classLoaderTest;
    }

    public ClassLoaderTest() {
        a++;
        b++;
    }
}

为什么会这样呢?

一个 java 类的完整的生命周期会经历加载、连接、初始化、使用、和卸载五大阶段。

加载:将.class字节码加载到jvm中,并生成一个class对象。

连接:进行验证(是否能被jvm执行,主要包括文件格式验证、元数据验证、字节码验证和符号引用验证)、准备(为静态变量分配内存)、解析(把类中的符号引用转换为直接引用)

初始化:为类的静态变量赋予用户定义的默认值和执行静态代码块

在例子中,在加载阶段将ClassLoaderTest加载到jvm中;在连接阶段对静态变量分配内存空间并初始化默认值,此时classLoaderTest = null;a =0;b=0;

main方法中通过

        ClassLoaderTest instance = getInstance();

调用该类的静态方法,触发了类的初始化,在初始化阶段对静态变量进行赋值。

1 初始化classLoaderTest  : classLoaderTest = new ClassLoaderTest();调用类的构造方法,a=1;b=1。

2 初始化a: a没有赋值操作,所以a=1;

3 初始化b:b=0;进行赋值。

所以最后的结果是1,0。

注:引用类的静态变量,不会导致子类初始化

 

再看一个例子:

下面是程序的输出结果:

为什么是这样呢?这儿引入一个“主动使用”的概念:

类只有在“主动使用”的时候才会进行初始化

1. 创建类的实例
2. 调用类的静态方法
3. 使用类的非常量静态字段
4. 调用Java API中的某些反射方法:如Class.forName()
5. 初始化某个类的子类
6. 含有main()方法的类启动时

返回来看上面的例子:

调用Teacher.age 此时会加载Teacher.java,由于Teacher.java继承了Person类,需要对Person.java进行加载成Person.class,并对父类进行初始化,初始化时会对类的静态变量赋值,以及执行静态代码块,此时输出Person static block。父类加载及初始化完成之后进行加载Teacher.java。加载成为Teacher.class之后,调用Teacher.age。该情况不属于上述主动使用的情况,所以不需要对Teacher.class进行初始化,也就不会有Teacher static block的输出。

类加载的时候可以通过class.forName来进行加载,也可以通过classloader.loadClass 来进行加载(loadClass加载的时候不会触发类的初始化)。

 

ClassLoader 类加载器

根据一个类的全限定名来读取此类的二进制字节流到 JVM 中,然后转换为一个与目标类对应的 java.lang.Class 对象,在 JVM 虚拟机提供了 3 种类加载器,启动(Bootstrap)类加载器(也称根类加载器)、扩展(Extension)类加载器、系统(System)类加载器(也称应用类加载器)

  • Bootstrap ClassLoader

Bootstrap ClassLoader 称为启动类加载器(也称根类加载器),是由 C++ 语言写,是 Java 虚拟机自身的一部分,它是在 Java 虚拟机启动后初始化的,用来装载核心类库,如 java.lang.*等,它主要负责加载 %JAVA_HOME%/jre/lib, %JAVA_HOME%/jre/classes,或者被-Xbootclasspath参数指定的路径的类。

  • Extension ClassLoader

ExtClassLoader 是用 Java 写的,由 Bootstrap ClassLoader 加载,并且将 ExtClassLoader 的父加载器设置为 Bootstrap ClassLoader,它 Launcher 类中定义的静态内部类,具体就是 sun.misc.Launcher$ExtClassLoader,主要负责加载 Java 的扩展类库,默认加载 JAVA_HOME/jre/lib/ext/ 目下的所有 jar 包以及 java.ext.dirs 系统变量指定的路径中类库。在Java9之后,更名为PlatformClassLoader
 

  • Application ClassLoader

Bootstrap ClassLoader 加载完 ExtClassLoader 后,就会加载 AppClassLoader,并且将 AppClassLoader 的父加载器指定为 ExtClassLoader。AppClassLoader 也是用 Java 写成的,它的实现类是 sun.misc.Launcher$AppClassLoader,另外在 ClassLoader 中提供了一个getSystemClassLoader()方法,此方法可用于获取 AppclassLoader。AppClassLoader 主要负责加载 classpath 所指定的位置的类或者是 jar 包,我们编写的 Java 程序也是由它加载。它也是我们实现自定义类加载器时默认的父加载器。

bootstrapClassLoader 是C++ 语言写的,这对 Java 是不可见的,所以最后一行的classLodaer为null。

所以如果某个 Class 对象的 classLoader 属性值是 null 时,那么就表示这个类也是根加载器加载的。

 

双亲委派机制

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

可以通过Classloader.loadClass()的源码看到此过程。

 

如果parent不为空时,通过parent的类加载器进行加载。如果为空,则使用启动类加载器进行加载。如果自带的类加载器无法加载该类的话,则通过 findClass()使用自定义加载器进行加载。

来说一下双亲委派的优点

1 可以避免重复加载

2 安全性:Java 核心基础 API 中定义类型不能被随意替换,假设通过网络传递一个名为 java.lang.Integer 的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心 Java API 发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的 java.lang.Integer,而直接返回已加载过的 Integer.class,这样便可以防止核心 API 库被随意篡改。

 

https://gitchat.csdn.net/activity/5cafebd20ebc85648e322910?utm_source=so

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

每年进步一点点

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值