Java应用环境中不同的class分别由不同的ClassLoader负责加载。JVM提供了三个默认的ClassLoader:Bootstrap ClassLoader、Extension ClassLoader、App ClassLoader, 其中Bootstrap ClassLoader是JVM级别的,由C++编写,Extension ClassLoader、App ClassLoader都是Java类,继承自URLClassLoader超类。Bootstrap ClassLoader由JVM启动,然后初始化sun.misc.Launcher, sun.misc.Launcher初始化Extension ClassLoader和App ClassLoader。
三个默认ClassLoader关系为:Bootstrap ClassLoader是Extension ClassLoader的parent,Extension ClassLoader是App ClassLoader的parent,但这并不是继承关系,只是语意上的定义,基本上每一个ClassLoader实现,都有一个Parent ClassLoader,可通过getParent()方法获取当前ClassLoader的parent。Bootstrap ClassLoader比较特殊,因为它不是Java class,所以Extension ClassLoader的getParent()方法返回NULL。如下:
public class Test {
public static void main(String [] args) {
ClassLoader cl = Test.class.getClassLoader();
while (cl != null) {
System.out.println(cl + "'s parent ClassLoader is " + cl.getParent());
cl = cl.getParent();
}
}
}
返回结果:
sun.misc.Launcher$AppClassLoader@18b4aac2's parent ClassLoader is sun.misc.Launcher$ExtClassLoader@4617c264
sun.misc.Launcher$ExtClassLoader@4617c264's parent ClassLoader is null
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
-
1. Bootstrap ClassLoader
Bootstrap ClassLoader称为启动类加载器,是Java类加载层次中最顶级的加载,负责加载JDK中的核心类库,如:rt.jar、resource.jar、charsets.jar等,Bootstrap类加载器的加载目录由系统属性(sun.boot.class.path)指定,目录如下:public class Test { public static void main(String [] args) { // 也可通过 System.getProperty("sun.boot.class.path" 获取 URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs(); for (int i = 0; i < urls.length; i++) { System.out.println(urls[i].toExternalForm()); } } } 本机JDK环境返回结果: file:/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/resources.jar file:/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/rt.jar file:/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/sunrsasign.jar file:/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/jsse.jar file:/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/jce.jar file:/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/charsets.jar file:/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/jfr.jar file:/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/classes
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
-
2. Extension ClassLoader
Extension ClassLoader称为扩展类加载器,负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext目录下的所有文件。Extension ClassLoader是JVM启动类(sun.misc.Launcher)的内部类,继承自java.net.URLClassLoader类。其加载目录由系统属性(java.ext.dirs)指定。本机加载目录如下:public class Test { public static void main(String [] args) { String paths = System.getProperty("java.ext.dirs"); String [] pathList = paths.split(File.pathSeparator); for(String path : pathList) { System.out.println(path); } } } 本机返回结果: /Users/Jerry/Library/Java/Extensions /Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/ext /Library/Java/Extensions /Network/Library/Java/Extensions /System/Library/Java/Extensions /usr/lib/java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
-
3.App ClassLoader
App ClassLoader称为系统类加载器,负责加载应用程序classpath目录下的所有jar和class文件。App ClassLoader既是加载系统属性(java.class.path)的类加载器,加载目录如下:package test; import java.io.File; public class Test { public static void main(String [] args) { String paths = System.getProperty("java.class.path"); String [] pathList = paths.split(File.pathSeparator); for(String path : pathList) { System.out.println(path); } } } 本机命令行编译并执行:java -cp /Users/Jerry/documents:/Users/Jerry/documents/workspace/xf-test test.Test 返回结果: /Users/Jerry/documents /Users/Jerry/documents/workspace/xf-test
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
如果不指定classpath,本地执行返回当前目录(”.”),如果通过IDE执行,则会返回IDE设定的classpath值
-
4.自定义ClassLoader
除了Java默认提供的三种ClassLoader外,用户也可以根据自己需要,通过继承ClassLoader超类定义属于自己的ClassLoader。范例如下:
定义自己的ClassLoader类,当通过ClassLoader.loadClass()方法加载类时,如果加载不到对应的类,则会调用自定义ClassLoader的findClass方法继续加载类,详见下一节:public class MyClassLoader extends ClassLoader{ public static final String driver = "/Users/Jerry/Desktop/"; public static final String fileTyep = ".class"; public Class findClass(String name) { byte[] data = loadClassData(name); return defineClass(data, 0, data.length); } public byte[] loadClassData(String name) { FileInputStream fis = null; byte[] data = null; try { File file = new File(driver + name + fileTyep); System.out.println(file.getAbsolutePath()); fis = new FileInputStream(file); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int ch = 0; while ((ch = fis.read()) != -1) { baos.write(ch); } data = baos.toByteArray(); } catch (IOException e) { e.printStackTrace(); System.out.println("loadClassData-IOException"); } return data; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
编写测试类,编译,并将class文件移动到在MyClassLoade的driver目录下:
package test; public class Test { public void sayHello(String name) { System.out.println("hello " + name); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
测试MyClassLoader:
public class Test { public static void main(String[] args) { MyClassLoader cl1 = new MyClassLoader(); try { Class c1 = cl1.findClass("Test"); // 或调用:Class c1 = cl1.loadClass("Test"); Object object = c1.newInstance(); Method method = c1.getMethod("sayHello", String.class); method.invoke(object, "Jerry"); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } 返回结果: /Users/Jerry/Desktop/Test.class hello Jerry
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
-
5.双亲委托模型
ClassLoader使用双亲委托模型来搜索类,当一个ClassLoader实例需要加载某个类时,它会检查是否已经加载过该类,如果没有加载过会把该任务委托给它的父类加载器,逐级向上检查是否已经加载过这个类。如果该类各级都未加载过,则自上而下尝试加载该类,任何一级加载到该类则返回,最终未加载到该类则返回ClassNotFoundException异常。
ClassLoader loadClass()方法原码如下,类加载大致过程为:
[1] 当前加载器检查类是否已被加载,如果已被加载则返回,否则交由父加载器加载
[2] 如果父加载器不为空,则由父加载加载,如果父加载器为空,则交由Bootstrap 加载器加载
[3] 如果父加载器加载失败,则尝试通过findClass()方法亲自加载类protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // [1] 检查类是否已被加载 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { // [2] 如果父加载器不为空,则交由父类加载 c = parent.loadClass(name, false); } else { // [3] 如果父类加载器为空,则交由Bootstrap ClassLoader加载 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { } if (c == null) { // [4] 如果委托的父加载器未加载到类,则尝试亲自加载类 long t1 = System.nanoTime(); c = findClass(name); 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
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
ClassLoader体系结构如下: