类加载器

类加载器

概述

前面一直在介绍类加载的过程,都是一些概念性的东西,但这些概念很重要。接下来我们来进行一些实战,研究一下类加载器。看清楚这是类加载器,不是类加载过程。

  1. 什么是类加载器
  2. 类加载器特点

1. 什么是类加载器

简单的说类加载器就是将.class文件加载进虚拟机并生成对应的Class对象。

下面这段话是摘自深入理解Java虚拟机

虚拟机设计团队把类加载阶段中通过一个类的权限定名来获取描述此类的二进制字节流这个动作放到java虚拟机外部实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的的代码快称为类加载器。

2 . 类加载器的特点

  • 层级结构:Java 里的类装载器被组织成了有父子关系的层级结构。Bootstrap 类装载器是所有装载器的父亲。
  • 代理模式: 基于层级结构,类的代理可以在装载器之间进行代理。当装载器装载一个类时,首先会检查它在父装载器中是否进行了装载。如果上层装载器已经装载了这个类,这个类会被直接使用。反之,类装载器会请求装载这个类
  • 可见性限制:一个子装载器可以查找父装载器中的类,但是一个父装载器不能查找子装载器里的类。
  • 不允许卸载:类装载器可以装载一个类但是不可以卸载它,不过可以删除当前的类装载器,然后创建一个新的类装载器装载。

接下来我将一一解释上面所列出来的特点,我们先来一个整体的了解JVM虚拟机

类加载器组织结构

这张图就可以很好的解释上面第一条:类加载器层级结构,他们之间的关系是:

  1. Bootstrap Classloader 是在Java虚拟机启动后初始化的。

  2. Bootstrap Classloader 负责加载 ExtClassLoader,并且将 ExtClassLoader的父加载器设置为 Bootstrap Classloader

  3. Bootstrap Classloader 加载完 ExtClassLoader 后,就会加载 AppClassLoader,并且将 AppClassLoader 的父加载器指定为 ExtClassLoader

    每一层类加载器的作用
Class Loader实现方式具体实现类负责加载的目标
Bootstrap LoaderC++由 C++ 实现%JAVA_HOME%/jre/lib/rt.jar以及-Xbootclasspath参数指定的路径以及中的类库
Extension ClassLoaderJavasun.misc.Launcher$ExtClassLoader%JAVA_HOME%/jre/lib/ext路径下以及java.ext.dirs系统变量指定的路径中类库
Application ClassLoaderJavasun.misc.Launcher$AppClassLoaderClasspath以及-classpath-cp指定目录所指定的位置的类或者是jar文档,它也是Java程序默认的类加载器

加载器的隔离问题

每个类装载器都有一个自己的命名空间用来保存已装载的类。当一个类装载器装载一个类时,它会通过保存在命名空间里的类全局限定名 (Fully Qualified Class Name) 进行搜索来检测这个类是否已经被加载了。

JVMDalvik 对类唯一的识别是 ClassLoader id + PackageName + ClassName,所以一个运行程序中是有可能存在两个包名类名完全一致的类的。并且如果这两个不是由一个 ClassLoader 加载,是无法将一个类的实例强转为另外一个类的,这就是 ClassLoader 隔离性。

下面通过一个代码来解释这个问题

package entry;

/*****************************************
*          username: zhangke
*          email   : 398757724@qq.com
*          date    : 18-8-8  下午2:48
*          intro   :
*****************************************/
public class Hello {
   public void say() {
       System.out.println("test");
   }
}
public class CustomClassLoader extends ClassLoader {
   private String classPath;

   public CustomClassLoader(ClassLoader parent, String classPath) {
       this.classPath = classPath;
   }

   public CustomClassLoader(String targetCodeLocaltion) {
       this.classPath = targetCodeLocaltion;
   }

   @SuppressWarnings("unchecked")
   public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException {

       Class scl = ClassLoader.getSystemClassLoader().loadClass("entry.Hello");
       customClassLoader = new CustomClassLoader("reflect/target/classes");
       Class cl1 = customClassLoader.findClass("entry.Hello");
       System.out.println(scl.getClassLoader());
       System.out.println(cl1.getClassLoader());
       System.out.println(scl == cl1);
   }

   @Override
   protected Class<?> findClass(String name) throws ClassNotFoundException {
       if (name != null) {
           byte[] data = loadClassData(name);
           if (data == null) {
               throw new ClassNotFoundException(name);
           }
           return defineClass(name, data, 0, data.length);
       }
       return null;
   }

   protected byte[] loadClassData(String name) {
       try {
           name = name.replace(".", "//");
           FileInputStream fis = new FileInputStream(new File(classPath + "//" + name + ".class"));
           ByteArrayOutputStream baos = new ByteArrayOutputStream();
           int len = -1;
           byte[] b = new byte[2048];
           while ((len = fis.read(b)) > 0) {
               baos.write(b, 0, len);
           }
           fis.close();
           return baos.toByteArray();
       } catch (IOException e) {
           e.printStackTrace();
       }
       return null;
   }
}

上面代码主要就是定义了一个类加载器,然后去读取编译出来的.class文件,生成一个class对象和使用系统默认加载进来生成的class对象进行对比,结果如下:

sun.misc.Launcher$AppClassLoader@18b4aac2
classLoader.CustomClassLoader@7f31245a
false

这个可以很好的显示类加载器的隔离性问题。

双亲委托机制

为了解决类加载器的隔离问题JVM引入了双亲委托机制。同时也是加载器有委托机制特点的由来。

核心思想:其一,自底向上检查类是否已加载;其二,自顶向下尝试加载类

具体加载过程
  1. AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派父类加载器ExtClassLoader去完成。
  2. ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派BootStrapClassLoader去完成。
  3. 如果BootStrapClassLoader加载失败(例如在%JAVA_HOME%/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;
  4. 如果ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException

具体源码如下

protected Class<?> loadClass(String name, boolean resolve)
       throws ClassNotFoundException
   {
       synchronized (getClassLoadingLock(name)) {
           // 首先检查请求的类是否已经加载
           Class<?> c = findLoadedClass(name);
           //没有加载,则可以加载
           if (c == null) { 
               long t0 = System.nanoTime();
               try {
                  //首先通过父类进行加载
                   if (parent != null) {
                       c = parent.loadClass(name, false);
                   } else { //如果是boot,则直接加载

                       c = findBootstrapClassOrNull(name);
                   }
               } catch (ClassNotFoundException e) {
                   //如果父类加载器跑出这个异常,则说明父类加载器无法完成加载请求
               }
              //如果父类加载器没有加载成功,则由当前加载器进行加载
               if (c == null) {
                   long t1 = System.nanoTime();
                   c = findClass(name);

                   // this is the defining class loader; record the stats
                   sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                   sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                   sun.misc.PerfCounter.getFindClasses().increment();
               }
           }
           //对请求加载的类进行连接
           if (resolve) {
               resolveClass(c);
           }
           return c;
       }
   }
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值