JVM学习之 类加载器ClassLoader

类的加载

一个类从被加载到虚拟机内存到卸载出内存,它整个的生命周期为:
在这里插入图片描述
其中连接又分为 验证、准备和解析

其中的加载过程就需要使用到类加载器
加载阶段需要完成三件事情:

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

ClassLoader就来做这三件事

类加载器

类加载器分成两类:启动类加载器用户自定义的类加载器
启动类加载器有三个: 启动类加载器(Bootstrap Class Loader)扩展类加载器(Extension Class Loader)应用程序类加载器(Application Class Loader)

  • 启动类加载器主要加载 <JAVA_HOME>\lib 下的Java类
  • 扩展类加载器主要负责加载 <JAVA_HOME>\lib\ext 目录中的类
  • 应用程序类加载器负责加载用户类路径(ClassPath)上的所有类库。

双亲委派模型:

上面所说的几种类加载器是有层次关系的:
在这里插入图片描述
这种层次关系也被称为 双亲委派模型

工作机制:

双亲委派模型的工作过程是 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。

这句话在ClassLoader 源码中也有体现:
Classloader.loadClass(String name, boolean resolve)方法的注释上有这么一句话:

 The default implementation of this method searches for classes in the  following order:
     *
     * <ol>
     *
     *   <li><p> Invoke {@link #findLoadedClass(String)} to check if the class has already been loaded.  </p></li>
     *
     *   <li><p> Invoke the {@link #loadClass(String) loadClass} method on the parent class loader.  If the parent is {@code null} the class  loader built into the virtual machine is used, instead.  </p></li>
     *
     *   <li><p> Invoke the {@link #findClass(String)} method to find the  class.  </p></li>
     *
     * </ol>
  1. 首先检查该类是否已经被加载
  2. 然后交给父加载器,如果父加载器为null,那么就使用虚拟机创建的类加载器
  3. 最后自己加载

注意: 有的问题说 父加载器找不到是返回null还是什么,我们观察源码可以发现:
catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
可以发现,如果父加载器找不到的话是直接抛出异常的

类加载器使用的策略

类加载器的一个重要用途是在JVM中为相同名称的Java类创建隔离空间。在JVM中,判断两个类是否相同,不仅是根据该类的全限定类名 ,还需要根据两个类的定义类加载器。只有两者完全一样,才认为两个类是相同的。因此,即便是同样的Java字节代码,被两个不同的类加载器定义之后,所得到的Java类也是不同的。如果试图在两个类的对象之间进行赋值操作,会抛出 java.lang.ClassCastException。

代码分析:

运行如下代码:
其中Person类可以自己定义

public class first {
    public static void main(String[] args) throws Exception{
        ClassLoader myLoader = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                try {
                    String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
                    InputStream is = getClass().getResourceAsStream(fileName);
                    if (is == null) {
                        return super.loadClass(name);
                    }
                    byte[] bytes = new byte[is.available()];
                    is.read(bytes);
                    return defineClass(name, bytes, 0, bytes.length);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return super.loadClass(name);
            }
        };
        Object o = myLoader.loadClass("com.example.demo4.Person").getConstructor().newInstance();
        System.out.println(o.getClass());
        System.out.println(o instanceof com.example.demo4.Person);
    }
}

运行结果如下;
在这里插入图片描述
第二行在和 com.example.demo4.Person进行所属类型检查时返回了false。这是因为前者是使用我们自定义的类加载器加载的,而后者是虚拟机的应用程序类加载器加载的,虽然 全限定类名都相同,都来自同一个Class文件,但是在虚拟机中仍然是两个独立的类,所以会判断false

自定义类加载器

我们上面自定义了自己的类加载器,但是只重写了loadClass()这一个方法,如果需要自定义,需要着重了解这些方法:

  • defineClass():这个方法用来完成从Java字节代码的字节数组到java.lang.Class的转换。这个方法是不能被覆写的,一般是用原生代码来实现的。
  • findLoadedClass():这个方法用来根据名称查找已经加载过的Java类。一个类加载器不会重复加载同一名称的类。
  • findClass():这个方法用来根据名称查找并加载Java类。
  • loadClass():这个方法用来根据名称加载Java类。
  • resolveClass():这个方法用来链接一个Java类。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值