java的classloader不求甚解

  先简单介绍下java的classloader,网上资料不少,就说点关键的。java

  Java 中的类加载器大体能够分红两类,一类是系统提供的,另一类则是由 Java 应用开发人员编写的。系统提供的类加载器主要有下面三个:bootstrap

  引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader。tomcat

  扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。安全

  系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。通常来讲,Java 应用的类都是由它来完成加载的。能够经过 ClassLoader.getSystemClassLoader() 来获取它。jvm

  除了系统提供的类加载器之外,开发人员能够经过继承 java.lang.ClassLoader 类的方式实现本身的类加载器,以知足一些特殊的需求。ide

  除了引导类加载器以外,全部的类加载器都有一个父类加载器。类加载采用委托模式,先一层一层交给父类加载,父加载不成功再一层一层转给子加载。函数

  要点1:为何采用这种委托方式,是为了安全,好比用户自定义了个java.lang.String,那么若是不交给引导类加载器去加载的话,内存中就会有不止一个String的类实例。并且一个限定包内访问权限的内容,黑客也能够用这种方式获取(要点2再继续说明)。采用了这种方式的话,引导类加载器只会加载一次类,看见用户自定义的String来了,就去看本身有没有加载,结果是系统一启动就加载了java.lang.String类,就不会再去加载了。spa

  要点2:判断一个类是否相等不只要看类是否名字同样,并且要看是否有同一个类初始化加载器。因此若是黑客要本身搞一个java.lang.Hack类来加载,由委托模式开始,引导类加载器加载这个类失败,那就只能交给用户自定义的类加载起来加载。因此这个类和系统的那个lang包里的类不在一个初始化加载器里,就算包名都同样,仍是不能访问那些包内可见的内容的。线程

------------------------------------------------------代理

进一步说明

一,有两个术语,一个叫“定义类加载器”,一个叫“初始类加载器”。
好比有以下的类加载器结构:
bootstrap
  ExtClassloader
    AppClassloader
    -自定义clsloadr1
    -自定义clsloadr2 
若是用“自定义clsloadr1”加载java.lang.String类,那么根据双亲委派最终bootstrap会加载此类,那么bootstrap类就叫作该类的“定义类加载器”,而包括bootstrap的全部获得该类class实例的类加载器都叫作“初始类加载器”。

二,所说的“命名空间”,是指jvm为每一个类加载器维护的一个“表”,这个表记录了全部以此类加载器为“初始类加载器”(而不是定义类加载器,因此一个类能够存在于不少的命名空间中)加载的类的列表,因此,题目中的问题就能够解释了:
CLTest是AppClassloader加载的,String是经过加载CLTest的类加载器也就是AppClassloader进行加载,但最终委派到bootstrap加载的(固然,String类其实早已经被加载过了,这里只是举个例子)。因此,对于String类来讲,bootstrap是“定义类加载器”,AppClassloader是“初始类加载器”。根据刚才所说,String类在AppClassloader的命名空间中(同时也在bootstrap,ExtClassloader的命名空间中,由于bootstrap,ExtClassloader也是String的初始类加载器),因此CLTest能够随便访问String类。这样就能够解释“处在不一样命名空间的类,不能直接互相访问”这句话了。

三,一个类,由不一样的类加载器实例加载的话,会在方法区产生两个不一样的类,彼此不可见,而且在堆中生成不一样Class实例。

四,那么由不一样类加载器实例(好比-自定义clsloadr1,-自定义clsloadr2)所加载的classpath下和ext下的类,也就是由咱们自定义的类加载器委派给AppClassloader和ExtClassloader加载的类,在内存中是同一个类吗?
全部继承ClassLoader而且没有重写getSystemClassLoader方法的类加载器,经过getSystemClassLoader方法获得的AppClassloader都是同一个AppClassloader实例,相似单例模式。
在ClassLoader类中getSystemClassLoader方法调用私有的initSystemClassLoader方法得到AppClassloader实例,在initSystemClassLoader中:
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
。。。
scl = l.getClassLoader();
AppClassloader是sun.misc.Launcher类的内部类,Launcher类在new本身的时候生成AppClassloader实例而且放在本身的私有变量loader里:
loader = AppClassLoader.getAppClassLoader(extclassloader);
值得一提的是sun.misc.Launcher类使用了一种相似单例模式的方法,即既提供了单例模式的接口getLauncher()又把构造函数设成了public的。可是在ClassLoader中是经过单件模式取得的Launcher 实例的,因此咱们写的每一个类加载器获得的AppClassloader都是同一个AppClassloader类实例。
这样的话获得一个结论,就是全部经过正常双亲委派模式的类加载器加载的classpath下的和ext下的全部类在方法区都是同一个类,堆中的Class实例也是同一个。

----------------------------------------

ContextClassLoader

每一个线程持有一个ContextClassLoader,能够用get,set方法获取或定义。若是不加指定,就是启动线程那么类本身的类加载器。若是不是main线程,new出来的线程的话,就是父线程的类加载器。

  为何要有这么一个东西呢,查了一些资料说是,由于为了安全ClassLoader的委托机制不能知足一些特定须要,这个时候就要用这种方式走后门。好比jdbc,jndi,tomcat等:

  Java 提供了不少服务提供者接口(Service Provider Interface,SPI),容许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。这些 SPI 的接口由 Java 核心库来提供,如 JAXP 的 SPI 接口定义包含在 javax.xml.parsers 包中。这些 SPI 的实现代码极可能是做为 Java 应用所依赖的 jar 包被包含进来,能够经过类路径(CLASSPATH)来找到。而问题在于,SPI 的接口是 Java 核心库的一部分,是由引导类加载器来加载的;SPI 实现的 Java 类通常是由系统类加载器来加载的。引导类加载器是没法找到 SPI 的实现类的,由于它只加载 Java 的核心库。它也不能代理给系统类加载器,由于它是系统类加载器的祖先类加载器。也就是说,类加载器的代理模式没法解决这个问题。

  线程上下文类加载器正好解决了这个问题。在 SPI 接口的代码中使用线程上下文类加载器,就能够成功的加载到 SPI 实现的类。线程上下文类加载器在不少 SPI 的实现中都会用到。

相关文章
相关标签/搜索