将class文件字节码内容加载到内存中,并将这些静态数据转换为方法区的运行时数据结构,在堆中生成一个表明这个类的java.lang.Class对象,做为方法区类数据访问的入口
类缓存 标准的JavaSE类记载器能够按照要求查找类,但一旦某个类被加载到类加载器中, 它将维持加载(缓存)一段时间。不过,JVM垃圾回收器能够回收这些Claas对象。java
引导类加载器 用来加载Java的核心库(JAVA_HOME/jre/lib/rt/jar,或sun.boot.class.path路径下的内容),是用原生的代码(c++)实现的,并不继承java.lang.ClassLoader。加载扩展类加载器和应用程序类加载器。并指定它们的父类加载器。mysql
扩展类记载器 用来加载Java的扩展库(JAVA_HOME/jre/ext/*.jar,或java.ext.dirs路径下的内容)。 Java虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载Java类。c++
应用程序类加载器 它根据Java应用的类路径(classpath,java.class.path路类) 通常来讲,Java应用的类都是由它来完成加载的。由sun.misc.Launcher$AppClassLoader实现。web
自定义类加载器 开发人员能够经过继承java.lang.ClassLoader类的方式实现本身的类加载器,以知足一些特殊的须要。sql
public class Demo { public static void main(String[] args) { //获取应用程序类加载器 System.out.println(ClassLoader.getSystemClassLoader()); //获取扩展类加载器 System.out.println(ClassLoader.getSystemClassLoader().getParent()); //获取引导类加载器 System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent()); //获取classpath() //System.out.println(System.getProperty("java.class.path")); } } //结果 sun.misc.Launcher$AppClassLoader@2503dbd3 sun.misc.Launcher$ExtClassLoader@511d50c0 null
public class FileSystemClassLoader extends ClassLoader { String rootDir; public FileSystemClassLoader(String rootDir) { this.rootDir = rootDir; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { Class c = findLoadedClass(name); if (c != null) { return c; } else { ClassLoader parent = this.getParent(); try { c = parent.loadClass(name); }catch (Exception e){ e.printStackTrace(); } if (c != null) { return c; } else { byte[] classData = getClassData(name); if (classData == null) { throw new ClassNotFoundException("自定义类加载器没有加载到"); } else { c = defineClass(name, classData, 0, classData.length); } } } return c; } private byte[] getClassData(String className) { String path = rootDir + "/" + className.replace(".", "/") + ".class"; InputStream is = null; ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { is = new FileInputStream(path); byte[] buffer = new byte[1024]; int temp = 0; while ((temp = is.read(buffer)) != -1) { baos.write(buffer,0,temp); } return baos.toByteArray(); } catch (Exception e) { return null; } finally { if(is != null){ try { is.close(); } catch (IOException e) { e.printStackTrace(); } } if(baos != null){ try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } } } public class TestMyClassLoader { public static void main(String[] args) throws ClassNotFoundException { FileSystemClassLoader loader = new FileSystemClassLoader("/Users/wjk/Desktop"); FileSystemClassLoader loader1 = new FileSystemClassLoader("/Users/wjk/Desktop"); Class c = loader.loadClass("com.Hello"); Class c1 = loader1.loadClass("com.Hello"); Class c2 = loader.loadClass("com.Hello"); Class c3 = loader.loadClass("java.lang.String"); System.out.println(c.hashCode());//被两个类加载器加载的同一个类,JVM认为是不一样的(c和c1的hashCode值不同) System.out.println(c1.hashCode()); System.out.println(c2.hashCode()); System.out.println(c.getClassLoader());//使用的是自定义的类加载器 System.out.println(c3.getClassLoader());//使用的是引导类加载器 } } //结果 1725154839 1670675563 1725154839 classLoaderTest.FileSystemClassLoader@5e2de80c null
代理模式:交给其余类加载器加载指定的类缓存
双亲委托机制 tomcat
(1)就是当某个特定的类加载器接到加载类的请求的时候,首先委托给其父类(父类若是有父类一直向上追溯),直到父类加载器没法加载时,该加载器进行加载。安全
(2)双亲委托机制是为了保证Java核心库的类型安全。服务器
这种机制保证不会加载到用户自定义的java.lang.Class类的状况 (3)类加载器除了用于加载类,也是安全最基本的屏障。数据结构
双亲委托机制是代理模式的一种,可是并非全部的类加载都是双亲委托机制,好比tomcat类加载器首先尝试特定的 类加载器,加载不到类时在尝试器父类加载器。
如何实现自定义类加载器: (1)继承java.lang.ClassLoader (2)检查所请求的类型是否已经被这个类加载器加载到命名空间,若是已经被加载直接返回。 (3)委派给父类加载(也能够不委派,这个程序控制)。 (4)调用自定义加载器findClass()方法获取字节码,而后调用defineClass()导入类型到方法区。
(1)通常状况下,保证一个类中所关联的其余类都是由当前类加载器所加载的。 例如:ClassA自己在扩展类加载器下找到,那么它里面new出来的一些也就只能用扩展类加载器查找( 不会低一个级别),因此有的应用程序类加载器能够找到,却没有找到。 (2)JDBC API。它有实现driven的部分(mysql/sql server),咱们的JDBC API都是有引导类加载器 或者扩展类加载器载入的,可是JDBC drive倒是由扩展类记载器或者应用程序类加载器载入,那么就有可能找不到drive中,在Java领域,其实只要分红Api+SPI(service provice interface特定厂商提供)的,都会遇到这个问题。简而言之:接口定义在Java自己,实现却在第三方,Java本自己使用引导类加载器或者扩展类加载器载入,而第三方实现使用扩展类加载器或者应用程序类记载器加载。
SPI接口是Java核心库的一部分,是由引导类架子器加载的;SPI实现的Java类通常是由应用程序类加载器加载的。引导类加载器是没法找到SPI的实现类的,由于它只加载Java的核心库。
(1)系统类加载器 (2)当前类加载器 (3)当前线程类加载器
线程类加载器是为了抛弃双亲委托加载链式模式。 每个线程都有一个关联上下文类加载器,若是用new Thread()方式生成 新的线程,新线程将继续继承其父线程的上下文类加载器。若是程序对线程上下文 类加载器没有任何变更的话,程序中全部的线程将都使用系统类加载器(即:应用程序类 加载器)做为上下文类加载器。
public class Demo3 { public static void main(String[] args) { //得到Demo类的类加载器 ClassLoader loader1 = Demo.class.getClassLoader(); System.out.println(loader1); //得到当前线程类加载器 ClassLoader loader2 = Thread.currentThread().getContextClassLoader(); System.out.println(loader2); //使用自定义类加载器 Thread.currentThread().setContextClassLoader(new FileSystemClassLoader("/Users/wjk/Desktop")); //得到当前线程类加载器 System.out.println(Thread.currentThread().getContextClassLoader()); } } //结果 sun.misc.Launcher$AppClassLoader@2503dbd3 sun.misc.Launcher$AppClassLoader@2503dbd3 classLoaderTest.FileSystemClassLoader@60e53b93
(1)一切为了安全,TOMCAT不能使用系统默认的类加载器。 缘由: 若是TOMCAT跑你的web项目的时候使用系统默认的类加载器, 你能够直接肆无忌惮的操做系统的各个目录,这是至关危险的。对于Java EE容器中的Web应用来讲,类加载器的实现 与通常Java应用有所不一样。每个Web应用都一个对应的类加载器,它先尝试加载某个类,加载不了再委托给父类加载器, (2)为了安全TOMCAT须要实现本身的类加载器 我能够限制你把类写在指定的位置,不然我不给你加载。
OSGI(Open Service Gateway Initative)是面向Java的动态模块系统。它为开发人员提供了 面向服务和基础组件的运行环境,并提供标准的方式用来管理软件的生命周期。
Eclipse就是基于OSGI技术构建的。
原理: OSGI中的每一个模块都包含Java包和类。模块能够声明它所依赖的须要导入的其余模块和Java包和 类,也能够声明本身导出的包和类,供其余模块来使用。也就是说可以隐藏和共享某些Java包和类。 这个是经过OSGI特有的类加载器机制来实现的。OSIG中每一个模块都有对应一个类加载器,它负责加载模块 本身包含的Java包和类。当它须要加载Java核心库(java开头的包和类)的类时,它要代理给父类加载器来完成。当它须要加载导入的Java类的时候,它会代理给导出此Java类的模块来完成加载,模块也能够显式声明某些类和包必须由父类加载器加载。