首先咱们要分析类加载原理,java中默认有三种类加载器:引导类加载器,扩展类加载器,系统类加载器(也叫应用类加载器) 引导类加载器负责加载jdk中的系统类,这种类加载器都是用c语言实现的,在java程序中没有办法得到这个类加载器,对于java程序是一个概念而已,基本上不用考虑它的存在,像String,Integer这样的类都是由引导类加载器加载器的. 扩展类加载器负责加载标准扩展类,通常使用java实现,这是一个真正的java类加载器,负责加载jre/lib/ext中的类,和普通的类加载器同样,其实这个类加载器对咱们来讲也不是很重要,咱们能够经过java程序得到这个类加载器。 系统类加载器,加载第一个应用类的加载器(其实这个定义并不许确,下面你将会看到),也就是执行java MainClass 时加载MainClass的加载器,这个加载器使用java实现,使用的很普遍,负责加载classpath中指定的类。 类加载器之间有必定的关系(父子关系),咱们能够认为扩展类加载器的父加载器是引导类加载器(固然不这样认为也是能够的,由于引导类加载器表如今java中就是一个null),不过系统类加载器的父加载器必定是扩展类加载器,类加载器在加载类的时候会先给父加载器一个机会,只有父加载器没法加载时才会本身去加载。 咱们没法得到引导类加载器,由于它是使用c实现的,并且使用引导类加载器加载的类经过getClassLoader方法返回的是null.因此没法直接操做引导类加载器,可是咱们能够根据Class.getClassLoader方法是否为null判断这个类是否是引导类加载器加载的,能够经过下面的方法得到引导类加载器加载的类路径(每一个jar包或者文件夹对应了一个URL); sun.misc.Launcher.getBootstrapClassPath().getURLs() 你能够直接在你的main函数中输出就能够了 System.out.println(java.util.Arrays.asList(sun.misc.Launcher.getBootstrapClassPath().getURLs()).toString()); 获得的结果是: [file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/rt.jar, file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/i18n.jar, file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/sunrsasign.jar, file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/jsse.jar, file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/jce.jar, file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/charsets.jar, file:/C:/Program%20Files/Java/j2re1.4.2_10/classes] 其实咱们是能够指定引导类加载器的类路径的,java提供了一个-Xbootclasspath参数,不过这个参数不是标准参数。 java -Xbootclasspath: 运行时指定引导类加载器的加载路径(jar文件或者目录) java -Xbootclasspath/p:和上面的相同,不过把这个路径放到原来的路径前面 java -Xbootclasspath/a:这个就是在原引导类路径后面添加类路径。 上面咱们有提过加载第一个应用类未必就是系统加载器。若是我把这个应用类的路径放到引导类路径中,它将会被引导类加载器加载,大体这样 java -Xbootclasspath/a:myjar.jar MainClass 若是MainClass在myjar.jar中,那么这个类将会被引导类加载器加载。 若是但愿看详情,使用-verbose参数,为了看的更清楚,使用重定向,大体为(windows下): java -verbose -Xbootclasspath/a:myjar.jar MainClass -> C:/out.txt 经过这个参数咱们能够实现本身的系统类,好比替换掉java.lang.Object的实现,本身能够扩展 一些方法,不过这样作彷佛没有好处,由于那就不是标准了。 咱们最关心的仍是系统类加载器,通常都认为系统类加载器是加载应用程序第一个类的加载器,也就是java MainClass命令中加载MainClass的类加载器,这种说法虽然不是很严谨,但基本上仍是能够这样认为的,由于咱们不多会改变引导类加载器和扩展类加载器的默认行为。应该说系统类加载器负责加载classpath路径中的并且没有被扩展类加载器加载的类(固然也包括引导类加载器加载的)。若是classpath中有这个类,可是这个类也在扩展类加载器的类路径,那么系统类加载器将没有机会加载它。 咱们不多改变扩展类加载器的行为,因此通常你本身定义的类都是系统类加载器加载器的。 得到系统类加载器很是简单,假设MyClass是你定义的一个类MyClass.class.getClassLoader()返回的就是系统类加载器,固然这种方法没法保证绝对正确,咱们可使用更简单并且必定正确的方式: ClassLoader.getSystemClassLoader()得到系统类加载器。咱们知道ClassLoader是一个抽象类,因此系统类加载器确定是ClassLoader的一个子类实现。咱们来看看它是什么 ClassLoader.getSystemClassLoader().getClass(); 结果是class sun.misc.Lancher$AppClassLoader 能够看出这是sun的一个实现,从名字能够看出是一个内部类,目前我也没有看到这个源代码,彷佛还不是很清晰:咱们在看看它的父类是什么: ClassLoader.getSystemClassLoader().getClass().getSuperclass(); 结果是:class java.net.URLClassLoader这个是j2se的标准类,它的父类是SecureClassLoader,而SecureClassLoader是继承ClassLoader的。如今整个关系应该很清楚,咱们会看到几乎全部的ClassLoader实现都是继承URLClassLoader的。由于系统类加载器是很是重要的,并且是咱们能够直接控制的,因此咱们后面还会介绍,不过先来看一下扩展类 加载器以及它们之间的关系。 扩展类加载器彷佛是一个不起眼的角色,它负责加载java的标准扩展(jre/lib/ext目录下的全部jar),它其实就是一个普通的加载器,看得见摸得着的。 首先的问题是怎么知道扩展类加载器在哪里?的确没有直接途径得到扩展类加载器,可是咱们知道它是系统类加载器的父加载器,咱们已经很容易的得到系统类加载器了,因此咱们能够间接的得到扩展类加载器: ClassLoader.getSystemClassLoader().getParent().getClass();实际上是经过系统类加载器间接的得到了扩展类加载器,看看是什么东西: 结果是:class sun.misc.Launcher$ExtClassLoader 这个类和系统类加载器同样是一个内部类,并且定义在同一个类中。一样看看它的父类是什么: ClassLoader.getSystemClassLoader().getParent().getClass().getSuperclass();能够看出结果也是class java.net.URLClassLoader扩展类加载jre/lib/ext目录下的全部类,包括jar,目录下的全部类(目录名不必定要classes).如今能够回答上面的问题了,你写一个HelloWorld,放到jre/lib/ext/下的某个目录好比 jre/lib/ext/myclass/HelloWorld.class而后在你classpath也设置一份到这个类的路径,结果执行java HelloWorld时,这个类是被扩展类加载器加载器的,能够这样证实 public static void main(String[] args){ System.out.println("loaded by"+HelloWorld.class.getClassLoader().getClass()); System.out.println("Hello World"); } 结果能够获得class sun.misc.Launcher$ExtClassLoader固然若是你把jre/lib/ext下myclass这个目录删除,仍然能够运行,可是这样结果是class sun.misc.Lancher$AppClassLoader若是你不知道这个过程的话,假设在你扩展类路径下有一份classpath中的拷贝,或者是比较低的版本,当你使用新的版本时会发现没有起做用,知道这个过程你就不会以为奇怪了。另外就是两个不一样的类加载器是能够加载一个同名的类的,也就是说虽然扩展类加载器加载了某个类,系统类加载器是能够加载本身的版本的,可是现有的实现都没有这样作,ClassLoader中的方法是会请求父类加载器先加载的,若是你本身定义类加载器彻底能够修改这种默认行为,甚至可让他没有父加载器。 这里给出一个方法如何得到扩展类加载器加载的路径: String path=System.getProperty("java.ext.dirs"); File dir=new File(path); if(!dir.exists()||!dir.isDirectory()){ return Collections.EMPTY_LIST; } File[] jars=dir.listFiles(); URL[] urls=new URL[jars.length]; for(int i=0;i<jars.length;i++){ urls[i]=sun.misc.URLClassPath.pathToURLs(jars[i].getAbsolutePath())[0]; } return Arrays.asList(urls); 对于扩展类加载器咱们基本上不会去关心,也不多把你本身的jar放到扩展路径,大部分状况下咱们都感受不到它的存在,固然若是你必定要放到这个目录下,必定要知道这个过程,它会优先于classpath中的类。 如今咱们应该很清楚知道某个类是哪一个加载器加载的,而且知道为何是它加载的,若是要在运行时得到某个类的类加载器,直接使用Class的getClassLoader()方法就能够了。 用户定义的类通常都是系统类加载器加载的,咱们不多直接使用类加载器加载类,咱们甚至不多本身加载类。由于类在使用时会被自动加载,咱们用到某个类时该类会被自动加载,好比new A()会致使类A自动被加载,不过这种加载只发生一次。咱们也可使用系统类加载器手动加载类,ClassLoader提供了这个接口ClassLoader.getSystemClassLoader().loadClass("classFullName");这就很明确的指定了使用系统类加载器加载指定的类,可是若是该类可以被扩展类加载器加载,系统类加载器仍是不会有机会的。咱们最经常使用的仍是使用Class.forName加载使用的类,这种方式没有指定某个特定的ClassLoader,会使用调用类的ClassLoader。也就是说调用这个方法的类的类加载器将会用于加载这个类。好比在类A中使用Class.forName加载类B,那么加载类A的类加载器将会用于加载类B,这样两个类的类加载器是同一个。 最后讨论一下如何得到某个类加载器加载了哪些类,这个彷佛有必定的使用价值,能够看出哪些类被加载了。其实这个也不是很难,由于ClassLoader中有一个classes成员变量就是用来保存类加载器加载的类列表,并且有一个方法void addClass(Class c) { classes.addElement(c);}这个方法被JVM调用。咱们只要利用反射得到classes这个值就能够了,不过classes声明为private的,咱们须要修改它的访问权限(没有安全管理器时很容易作到) classes = ClassLoader.class.getDeclaredField("classes"); classes.setAccessible(true); List ret=(List) classes.get(cl); //classes是一个Vector 惋惜的是对于引导类加载器没有办法得到加载的类,由于它是c实现的,在java中很难控制了。