类加载器,通过一个类的全限定名来获取描述此类的二进制字节流
第一类是启动类加载器(Bootstrap ClassLoader):这个类加载器主要加载JVM自身工作需要的 类。这个类加载器由C++语言实现(特指HotSpot),是虚拟机自身的一部分。负责将存放在%JAVA_HOME%lib目录中的,或者被 -Xbootclasspath参数所指定的路径中,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不 会被加载)类加载到虚拟机内存中。
另一类就是所有其他的类加载器,这些加载器都是由java实现,独立于虚拟机外部。
Extension ClassLoader:这个类即在其有sun.misc.Launcher$ExtClassLoader实现,它负责加载%JAVA_HOME%libext目录中,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
Application ClassLoader:这个类加载器由sun.misc.Launcher$AppClassLoader
实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也成它为系统类加载器。它负责
加载用户类路径上指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
AppClassLoader的parent是ExtClassLoader。很多文章在介绍ClassLoader层次的结构时把
Bootstrap ClassLoader也列在ExtClassLoader的上一级中,其实Bootstrap
ClassLoader并不属于JVM的类等级层次,因为Bootstrap
ClassLoader没有遵守ClassLoader的加载股则。另外Bootstrap
ClassLoader没有子类,ExtClassLoader的父类也不是Bootstrap
ClassLoader,ExtClassLoader并没有父类,我们在应用中能提取到的顶层父类是ExtClassLoader.
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的 类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它搜索范围中没有找到所需的 类)时,子加载器才会尝试自己去加载。
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. 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; } }
自定义类加载器:
findClass()的功能是找到class文件并把字节码加载到内存中。自定义的ClassLoader一般覆盖改方法,以便使用不同的加载路径,然后调用defineClass()解析字节码。
defineClass()方法用来将byte字节流解析成JVM能够识别的Class对象。有了这个方法意味着我们不仅仅可以通过class文件实
例化对象,还可以通过其他方式实例化对象,如我们通过网络接收到一个类的字节码,拿这个字节码流直接创建类的Class对象形式实例化对象。
自定义的加载器可以覆盖方法loadClass()以便定义不同的加载机制。
如果自定义的加载器仅覆盖了findClass(),而未覆盖loadClass(即加载规则一样,但加载路径不同);则调用
getClass().getClassLoader()返回的仍然是AppClassLoader!因为真正的load类,还是
AppClassLoader.
package classloader; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; public class ClassReloader extends ClassLoader { private String classPath; String classname = "classloader.SingleClass"; public ClassReloader(String classpath) { this.classPath = classpath; } protected Class<?> findClass(String name) throws ClassNotFoundException{ byte [] classData = getData(name); if(classData == null) { throw new ClassNotFoundException(); } else { return defineClass(classname,classData,0,classData.length); } } private byte[] getData(String className) { String path = classPath+className; try { InputStream is = new FileInputStream(path); ByteArrayOutputStream stream = new ByteArrayOutputStream(); byte[] buffer = new byte[2048]; int num = 0; while((num = is.read(buffer))!=-1) { stream.write(buffer,0,num); } return stream.toByteArray(); } catch(IOException e) { e.printStackTrace(); } return null; } public static void main(String[] args) { try { String path = "D:/workspace_jee/JavaTest/src/classloader/"; ClassReloader reloader = new ClassReloader(path); Class r = reloader.findClass("SingleClass.class"); System.out.println(r.newInstance()); // ClassReloader reloader2 = new ClassReloader(path); Class r2 = reloader.findClass("SingleClass.class"); System.out.println(r2.newInstance()); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { e.printStackTrace(); } } }