Java中的类加载器

类加载的三个阶段

  1. 加载(Loading): 查找并加载类的二进制数据
  2. 链接(Linking):
  • 验证(Verifying):确保被加载类的正确性(防止恶意文件被JVM加载
  • 准备(Preparing):为类的静态变量分配内存,并将其初始化为默认值
  • 解析(Resolving):把类中的符号引用转换为直接引用
  1. 初始化:为类的静态变量赋予正确的初始值

主动使用的分类

  1. new,直接使用
  2. 访问某个类或接口的静态变量,或者对该静态变量进行赋值操作
  3. 调用静态方法
  4. 反射某个类
  5. 初始化一个子类
  6. 启动类, 比如 java HelloWorld

加载类的方式

  1. 本地磁盘中直接加载
  2. 内存中直接加载
  3. 通过网络加载.class
  4. 从zip,jar等归档文件中加载.class
  5. 数据库中提取.class文件
  6. 动态编译

剖析类加载的过程

加载

加载的最终产物是位于堆区的Class对象

类的二进制文件存储在方法区中(数据结构),然后再堆区中创建一个Class对象,这个对象作为程序访问方法区中这些数据结构的外部接口

链接

验证

  1. 魔术因子 0xCAFEBABY
  2. 主从版本号(当前虚拟机能否支持启动这个类)
  3. 常量池中的常量类型
  4. 元数据验证
  • 是否有父类
  • 父类是不是允许继承
  • 是否实现了抽象方法
  • 是否覆盖了父类的final字段
  • 其他的语义检查
  1. 字节码验证
  2. 符号引用验证

准备

准备阶段就是给类变量分配内存,并初始化默认值

解析

  • 类或者接口的解析
  • 字段解析
  • 类方法解析
  • 接口方法解析

在这一步会将符号引用转换为直接引用
比如调用System.out.println() 会生成get stack system.out类似的字眼

初始化

执行构造函数<clinit>()
虚拟机保证初始化对象时是线程安全的, 即一个类有两个线程同时初始化对象,只有一个线程能进

JVM类加载器介绍

k2rK6x.jpg

根类加载器

获取核心类库

System.out.println(System.getProperty("sun.boot.class.path"));

结果

D:\Java\jdk\jre\lib\resources.jar;D:\Java\jdk\jre\lib\rt.jar;D:\Java\jdk\jre\lib\sunrsasign.jar;D:\Java\jdk\jre\lib\jsse.jar;D:\Java\jdk\jre\lib\jce.jar;D:\Java\jdk\jre\lib\charsets.jar;D:\Java\jdk\jre\lib\jfr.jar;D:\Java\jdk\jre\classes

扩展类加载器

获取扩展类库

System.out.println(System.getProperty("java.ext.dirs"));

结果

D:\Java\jdk\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext

双亲委派机制

  1. 父子类加载器之间的真实关系- 包装关系
  2. 父委托机制的优点就是能够提高系统的安全性,在此机制下,用户自定义的类加载器不可能加载 应该由父加载器的可靠类,因此可以防止恶意的代码代替父加载器的可靠代码

看源码知过程

  1. 首先检查当前类加载器是否已经缓存过类
  2. 发现父加载器,则递归进行加载
  3. 如果当前不存在父加载器,直接调用根加载器对类进行加载
  4. 如果当前所有父类都没加载成功,那么直接调用当前的类加载器进行加载
  5. 如果类被成功加载,做一些性能数据的统计

如何改变赴委托机制

重写loadClass()方法

@Override
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException {
    Class<?> clazz = null;
    if (name.startsWith("java.")) {
        try {
                ClassLoader system = ClassLoader.getSystemClassLoader();
            clazz = system.loadClass(name);
            if (clazz != null) {
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }
        } catch (Exception e) {
            //ignore
        }
    }


    try {
        clazz = findClass(name);
    } catch (Exception e) {
        e.printStackTrace();
    }

    if (clazz == null && getParent() != null) {
        getParent().loadClass(name);
    }

    return clazz;
}

双亲委托机制好处

  1. 保护正常系统类的安全性
  2. 避免有些类被重复加载

命名空间

命名空间是由该加载器以及所有父加载器所加载的类构成的。

运行时包

由同一类加载器加载的属于相同包的类组成了运行时包。(包+类加载器) 只有属于同一运行时包的类间才能相互访问可见。

由于java.lang.TestCase和核心类库java.lang.*由不同的类加载器加载,他们属于不同的运行时包,所以,java.lang.TestCase不能访问java.lang包中的包可见成员。

类卸载

满足下面三个条件才会被GC

  1. 该类的所有实例都已经被GC
  2. 加载该类的ClassLoader实例被回收
  3. 该类的class实例没有在其他地方被引用
posted @ 2019-02-21 14:37  Draymonder  阅读(361)  评论(0编辑  收藏  举报