Java ClassLoader深入研究

Java为了提供平台无关性,在操作系统之上加入了一层JVM来隔离操作系统特定实现,使所编写的java代码在任何平台都能运行,但是JVM是特定于某一操作系统的

 

 

一、当JVM启动时,由三个类加载器对类进行加载:
1.bootstrap classloader

2.extension classloader
3.system classloader

 

(1)bootstrap classloader[ 引导类加载器] 是由JVM实现的,不是java.lang.ClassLoader的子类 ,它负责加载Java的核心类,其加载的类由 sun.boot.class.path指定,或者在 执行java命令时使用-Xbootclasspath选项或使用-D选项指定sun.boot.class.path系统属性值

 

说明:

-Dproperty_name=property_value  指定属性的值;

-Xbootclasspath   改变虚拟机装载缺省系统运行包rt.jar,而从-Xbootclasspath中设定的搜索路径中装载系统运行类

 

输出加载的核心类库:

 

[java] view plaincopy
 
  1. public class TestBootstrapClassLoader {  
  2.     public static void main(String[] args) {  
  3.         URL[] urls=sun.misc.Launcher.getBootstrapClassPath().getURLs();  
  4.         for (int i = 0; i < urls.length; i++) {  
  5.           System.out.println(urls[i].toExternalForm());  
  6.         }  
  7.     }  
  8. }  

 

输出结果:

file:/E:/MyEclipse%205.1.1%20GA/jre/lib/rt.jar
file:/E:/MyEclipse%205.1.1%20GA/jre/lib/i18n.jar
file:/E:/MyEclipse%205.1.1%20GA/jre/lib/sunrsasign.jar
file:/E:/MyEclipse%205.1.1%20GA/jre/lib/jsse.jar
file:/E:/MyEclipse%205.1.1%20GA/jre/lib/jce.jar
file:/E:/MyEclipse%205.1.1%20GA/jre/lib/charsets.jar
file:/E:/MyEclipse%205.1.1%20GA/jre/classes

 

(2) extension classloader[扩展类加载器] ,它负责加载JRE的扩展目录(JAVA_HOME/jre/lib/ext或者由java.ext.dirs系统属性指定的)中JAR的类包。这为引入除Java核心类以外的新功能提供了一个标准机制。在这个实例上调用方法getParent()总是返回空值null,因为引导加载器bootstrap classloader不是一个真正的ClassLoader实例。

 

[java] view plaincopy
 
  1. class TestExtensionClassLoader {  
  2.     public static void main(String[] args) {  
  3.         System.out.println(System.getProperty("java.ext.dirs"));  
  4.         ClassLoader extensionClassloader=ClassLoader.getSystemClassLoader().getParent();  
  5.         System.out.println("the parent of extension classloader : "+extensionClassloader.getParent());  
  6.     }  
  7. }  

 

输出结果:

E:/MyEclipse 5.1.1 GA/jre/lib/ext
the parent of extension classloader : null

 

以上这段代码也表明了下列父子关系或加载顺序:

bootstrap classloader(因其不是ClassLoader的子类,null)-->extension classloader-->system classloader

这也表明了jvm在加载类的顺序,当加载一个类时(假设其未加载),先找到最顶层的classloader,如果其可以加载这个类(或者已经加载了这个类),则返回这个类;如果其不能加载类(换个说法:在相应路径中搜索不到相应类),则用其子classloader加载,直到这个类被加载或者抛出相应的异常

这个顺序保证了越重要的类,越先加载;因为一个类只被加载一次(cache),所以如java.lang.System这个类,不能被用户替换(因为是按照bootstrap-->extension-->system的顺序,当要加载java.lang.System类时,其首先从bootstrap的搜索路径中找类)

 

(3)system/application classloader [系统(应用)类加载器] ,加载来自-classpath或者java.class.path系统属性或者CLASSPATH操作系统属性所指定的JAR类包和类路径。

a.可以通过静态方法ClassLoader.getSystemClassLoader()找到该类加载器。

b.如果没有特别指定,则用户自定义的任何类加载器都将该类加载器作为它的父加载器。可以从源代码看出这一点:

 

[java] view plaincopy
 
  1. protected ClassLoader() {  
  2.     SecurityManager security = System.getSecurityManager();  
  3.     if (security != null) {  
  4.         security.checkCreateClassLoader();  
  5.     }  
  6.     this.parent = getSystemClassLoader();  
  7.     initialized = true;  
  8.     }  

 

classpath路径:

 

[java] view plaincopy
 
  1. class TestSystemClassLoader {  
  2.     public static void main(String[] args) {  
  3.         System.out.println(System.getProperty("java.class.path"));  
  4.     }  
  5. }  


结果为:

 

E:/dev/java/eclipse/TestClassLoader/classes;E:/dev/java/lib/commons-logging-1.1.jar

 

重要:

classloader加载类用的是全盘负责委托机制。

a.全盘负责,即是当一个classloader加载一个Class的时候,这个Class所依赖的和引用的所有Class也由这个classloader负责载入,除非是显式的使用另外一个classloader载入 ;

b.委托机制则是先让parent类加载器 (而不是super,它与parent classloader类不是继承关系)寻找,只有在parent找不到的时候才从自己的类路径中去寻找。此外类加载还采用了cache机制,也就是如果 cache中保存了这个Class就直接返回它,如果没有才从文件中读取和转换成Class,并存入cache,这就是为什么我们修改了Class但是必须重新启动JVM才能生效的原因。

二、ClassLoader加载Class的过程 
1.检测此Class是否载入过(即在cache中是否有此Class),如果有到8,如果没有到2
2.如果parent classloader不存在(没有parent,那parent一定是bootstrap classloader了),到4
3.请求parent classloader载入,如果成功到8,不成功到5
4.请求jvm从bootstrap classloader中载入,如果成功到8
5.寻找Class文件(从与此classloader相关的类路径中寻找)。如果找不到则到7.
6.从文件中载入Class,到8.
7.抛出ClassNotFoundException.
8.返回Class.

 

源代码如下:

 

[java] view plaincopy
 
  1. ClassLoader类中  
  2. protected synchronized Class<?> loadClass(String name, boolean resolve)  
  3.     throws ClassNotFoundException  
  4.     {  
  5.     // First, check if the class has already been loaded  
  6.     Class c = findLoadedClass(name);  
  7.     if (c == null) {  
  8.         try {  
  9.         if (parent != null) {  
  10.             c = parent.loadClass(name, false);  
  11.         } else {  
  12.             c = findBootstrapClass0(name);  
  13.         }  
  14.         } catch (ClassNotFoundException e) {  
  15.             // If still not found, then invoke findClass in order  
  16.             // to find the class.  
  17.             c = findClass(name);  
  18.         }  
  19.     }  
  20.     if (resolve) {  
  21.         resolveClass(c);  
  22.     }  
  23.     return c;  
  24.     }  



可以看到当在parent链classloader中和bootstrap classloader中都找不到相应的类时,会调用findClass方法,因此ClassLoader的子类可以重写这个方法,定义自己的找到类的方法 

 

 

 

以下是一些类的ClassLoader示例:

 

[java] view plaincopy
 
  1. class TestClassLoader {  
  2.     public static void main(String[] args) {  
  3.         System.out.println(System.class.getClassLoader());  
  4.         System.out.println(javax.swing.JButton.class.getClassLoader());  
  5.         System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader());  
  6.         System.out.println(org.apache.commons.logging.Log.class.getClassLoader());  
  7.     }  
  8. }  

 

输出:

null        其路径在sun.boot.class.path中,由bootstrap classloader加载,所以返回为null(因其不是ClassLoader的子类)

null        其路径在sun.boot.class.path中,由bootstrap classloader加载 
sun.misc.Launcher$ExtClassLoader@7259da      路径在java.ext.dirs中,由extension classloader加载
sun.misc.Launcher$AppClassLoader@197d257   由classpath指定,由system classloader加载

 

查看sun.misc.Launcher的源代码

 

[java] view plaincopy
 
  1. public Launcher() {  
  2.     // Create the extension class loader  
  3.     ClassLoader extcl;  
  4.     try {  
  5.         extcl = ExtClassLoader.getExtClassLoader();  
  6.     } catch (IOException e) {  
  7.         throw new InternalError(  
  8.         "Could not create extension class loader");  
  9.     }  
  10.     // Now create the class loader to use to launch the application  
  11.     try {  
  12.         loader = AppClassLoader.getAppClassLoader(extcl);  
  13.     } catch (IOException e) {  
  14.         throw new InternalError(  
  15.         "Could not create application class loader");  
  16.     }  
  17.     // Also set the context class loader for the primordial thread.  
  18.     Thread.currentThread().setContextClassLoader(loader);  
  19.         // 其他  
  20. }  

 

可以看到是由Launcher这个类初始化ExtClassLoader和AppClassLoader类的

ExtClassLoader无parent,而AppClassLoader的parent为 ExtClassLoader

 

Launcher的getClassLoader()方法

 

[java] view plaincopy
 
  1. public ClassLoader getClassLoader() {  
  2.     return loader;  
  3.     }  
  4. 只返回AppClassLoader  

 

 

ExtClassLoader

 

[java] view plaincopy
 
  1. static class ExtClassLoader extends URLClassLoader {  
  2.     private File[] dirs;  
  3.     /** 
  4.      * create an ExtClassLoader. The ExtClassLoader is created 
  5.      * within a context that limits which files it can read 
  6.      */  
  7.     public static ExtClassLoader getExtClassLoader() throws IOException  
  8.     {  
  9.         final File[] dirs = getExtDirs();  
  10.         try {  
  11.         // Prior implementations of this doPrivileged() block supplied   
  12.         // aa synthesized ACC via a call to the private method  
  13.         // ExtClassLoader.getContext().  
  14.         return (ExtClassLoader) AccessController.doPrivileged(  
  15.              new PrivilegedExceptionAction() {  
  16.             public Object run() throws IOException {  
  17.                             int len = dirs.length;  
  18.                             for (int i = 0; i < len; i++) {  
  19.                                 MetaIndex.registerDirectory(dirs[i]);  
  20.                             }  
  21.                             return new ExtClassLoader(dirs);  
  22.             }  
  23.             });  
  24.         } catch (java.security.PrivilegedActionException e) {  
  25.             throw (IOException) e.getException();  
  26.         }  
  27.     }  
  28. ......  
  29. private static File[] getExtDirs() {  
  30.         String s = System.getProperty("java.ext.dirs");  
  31.         File[] dirs;  
  32.         if (s != null) {  
  33.         StringTokenizer st =   
  34.             new StringTokenizer(s, File.pathSeparator);  
  35.         int count = st.countTokens();  
  36.         dirs = new File[count];  
  37.         for (int i = 0; i < count; i++) {  
  38.             dirs[i] = new File(st.nextToken());  
  39.         }  
  40.         } else {  
  41.         dirs = new File[0];  
  42.         }  
  43.         return dirs;  
  44.     }  

 

 

AppClassLoader

 

[java] view plaincopy
 
  1. static class AppClassLoader extends URLClassLoader {  
  2.     public static ClassLoader getAppClassLoader(final ClassLoader extcl)  
  3.         throws IOException  
  4.     {  
  5.         final String s = System.getProperty("java.class.path");  
  6.         final File[] path = (s == null) ? new File[0] : getClassPath(s);  
  7.         // Note: on bugid 4256530  
  8.         // Prior implementations of this doPrivileged() block supplied   
  9.         // a rather restrictive ACC via a call to the private method  
  10.         // AppClassLoader.getContext(). This proved overly restrictive  
  11.         // when loading  classes. Specifically it prevent  
  12.         // accessClassInPackage.sun.* grants from being honored.  
  13.         //  
  14.         return (AppClassLoader)   
  15.         AccessController.doPrivileged(new PrivilegedAction() {  
  16.         public Object run() {  
  17.             URL[] urls =  
  18.             (s == null) ? new URL[0] : pathToURLs(path);  
  19.             return new AppClassLoader(urls, extcl);  
  20.         }  
  21.         });  
  22.     }  
  23. ......  

 

 

Launcher类的getBootstrapClassPath()方法

 

[java] view plaincopy
 
  1. public static URLClassPath getBootstrapClassPath() {  
  2.     String prop = (String)AccessController.doPrivileged(new GetPropertyAction("sun.boot.class.path"));  
  3.     URL[] urls;  
  4.     if (prop != null) {  
  5.         final String path = prop;  
  6.         urls = (URL[])AccessController.doPrivileged(  
  7.         new PrivilegedAction() {  
  8.             public Object run() {  
  9.                         File[] classPath = getClassPath(path);  
  10.                         int len = classPath.length;  
  11.                         Set seenDirs = new HashSet();  
  12.                         for (int i = 0; i < len; i++) {  
  13.                             File curEntry = classPath[i];  
  14.                             // Negative test used to properly handle  
  15.                             // nonexistent jars on boot class path  
  16.                             if (!curEntry.isDirectory()) {  
  17.                                 curEntry = curEntry.getParentFile();  
  18.                             }  
  19.                             if (curEntry != null && seenDirs.add(curEntry)) {  
  20.                                 MetaIndex.registerDirectory(curEntry);  
  21.                             }  
  22.                         }  
  23.                         return pathToURLs(classPath);  
  24.             }  
  25.         }  
  26.         );  
  27.     } else {  
  28.         urls = new URL[0];  
  29.     }  
  30.     return new URLClassPath(urls, factory);  
  31.     }  

 

从以上代码中可以看到bootstrap classloader使用sun.boot.class.path来加载类,extension classloader使用java.ext.dirs来加载类,而system classloader使用java.class.path来加载类

 

运行下列程序:

 

[java] view plaincopy
 
  1. class Test1 {  
  2.     public static void main(String[] args) {  
  3.         System.out.println(sun.misc.Launcher.getLauncher().getClass().getClassLoader());  
  4.     }  
  5. }  

 

结果为:

null

表明Launcher是由bootstrap classloader来加载的

 

三、关于Context ClassLoader

在上面Launcher的构造函数中有这么一句: Thread.currentThread().setContextClassLoader(loader); 这句是设置当前线程的classloader,默认是使用的AppClassLoader

这个有什么作用呢?

当线程需要用到某个类,contextClassLoader被请求来载入该类

注意:

(1)Class.forName(String name)载入的是在系统中已经加载入sun.boot.class.path、 java.ext.dirs、java.class.path路径中的类,而在这几个路径中未加入的类不能载入(报异常)

(2)Class.forName(String name, boolean initialize, ClassLoader loader)可以载入上述三个路径中没有的类,只要指定你的classloader即可

(3)利用ClassLoader可以载入在上述三个路径中没有的类

示例:

 

[java] view plaincopy
 
  1. 自定义ClassLoader,从c根目录中读class  
  2. package Test;  
  3. public class TestClassLoader extends ClassLoader {  
  4.         protected Map<String, Class> cache = new HashMap<String, Class>();  
  5.       
  6.     public TestClassLoader() {  
  7.         super();  
  8.     }  
  9.       
  10.     protected Class<?> findClass(String name) throws ClassNotFoundException {  
  11.                 if (cache.get(name) != null) {  
  12.             return cache.get(name);  
  13.         }  
  14.         try {  
  15.             String tname = name.replace('.', '/');  
  16.             File file = new File("c://" + tname + ".class");  
  17.             FileInputStream in = new FileInputStream(file);  
  18.             BufferedInputStream bufIn = new BufferedInputStream(in);  
  19.             ByteArrayOutputStream byteOut = new ByteArrayOutputStream();  
  20.             BufferedOutputStream bufOut = new BufferedOutputStream(byteOut);  
  21.             byte[] buffer = new byte[4096];  
  22.             int len = -1;  
  23.             while((len = bufIn.read(buffer)) != -1) {  
  24.                 bufOut.write(buffer, 0, len);  
  25.             }  
  26.             bufOut.flush();  
  27.             byteOut.flush();  
  28.             byte[] data = byteOut.toByteArray();  
  29.             Class cls = defineClass(name, data, 0, data.length);  
  30.             cache.put(name, cls);  
  31.             return cls;     } catch (IOException e) {  
  32.             e.printStackTrace();  
  33.         }  
  34.         return null;  
  35.     }  
  36. }  
  37. class TestLoader {  
  38.     public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {  
  39.         ClassLoader cl = Thread.currentThread().getContextClassLoader();  
  40.         System.out.println(Thread.currentThread().toString() + "  " + cl.toString());  
  41.         new Thread() {  
  42.             public void run() {  
  43.                 ClassLoader cl = Thread.currentThread().getContextClassLoader();  
  44.                 System.out.println(Thread.currentThread().toString() + "  " + cl.toString());  
  45.                   
  46.                 TestClassLoader loader = new TestClassLoader();  
  47.                 Thread.currentThread().setContextClassLoader(loader);  
  48.                 new Thread() {  
  49.                     public void run() {  
  50.                         try {  
  51.                             ClassLoader cl = Thread.currentThread().getContextClassLoader();  
  52.                             System.out.println(Thread.currentThread().toString() + "  " + cl.toString());  
  53.     //ClassLoader                         
  54.           AbstractTestA test = (AbstractTestA) cl.loadClass("Test.TestAImp1").newInstance();  
  55.         //Class.forName  
  56.     //AbstractTestA test = (AbstractTestA) Class.forName("Test.TestAImp1", true, cl).newInstance();                     test.test();  
  57.                         } catch (InstantiationException e) {  
  58.                             // TODO Auto-generated catch block  
  59.                             e.printStackTrace();  
  60.                         } catch (IllegalAccessException e) {  
  61.                             // TODO Auto-generated catch block  
  62.                             e.printStackTrace();  
  63.                         } catch (ClassNotFoundException e) {  
  64.                             // TODO Auto-generated catch block  
  65.                             e.printStackTrace();  
  66.                         }  
  67.                     }  
  68.                 }.start();  
  69.             }  
  70.         }.start();  
  71.     }  
  72. }  
  73. //抽象类  
  74. package Test;  
  75. public abstract class AbstractTestA {  
  76.     public String a;  
  77.     public void print() {  
  78.         System.out.println("a=" + a);  
  79.     }  
  80.       
  81.     abstract public void test();  
  82. }  
  83. //编译后放入c根目录  
  84. package Test;  
  85. public class TestAImp1 extends AbstractTestA {  
  86.     public void test() {  
  87.         System.out.println(Thread.currentThread().toString());  
  88.         System.out.println("Test");  
  89.     }  
  90. }  

 

运行结果为:

Thread[main,5,main]  sun.misc.Launcher$AppClassLoader@197d257
Thread[Thread-0,5,main]  sun.misc.Launcher$AppClassLoader@197d257
Thread[Thread-1,5,main]  Test.TestClassLoader@1004901
Thread[Thread-1,5,main]
Test

这也表明了线程间ContextClassLoader的继承性:

(1)main线程默认的 ContextClassLoader为AppClassLoader

(2)新启动线程从原线程处继承 ContextClassLoader

 

四、关于ClassLoader和Package

其中:String(int offset, int len, char[] arr)为包访问权限

 

[java] view plaincopy
 
  1. package java.lang;  
  2. public class TestPackage {  
  3.     public static void main(String[] args) {  
  4.         char[] c = "1234567890".toCharArray();  
  5.         String s = new String(0, 10, c);  
  6.     }  
  7. }  

 

此代码可以编译通过,但是运行时出现下列错误:

java.lang.SecurityException: Prohibited package name: java.lang
 at java.lang.ClassLoader.preDefineClass(Unknown Source)
 at java.lang.ClassLoader.defineClass(Unknown Source)
 at java.security.SecureClassLoader.defineClass(Unknown Source)
 at java.net.URLClassLoader.defineClass(Unknown Source)
 at java.net.URLClassLoader.access$100(Unknown Source)
 at java.net.URLClassLoader$1.run(Unknown Source)
 at java.security.AccessController.doPrivileged(Native Method)

 

这表明:

Java语言规定,在同一个包中的class,如果没有修饰符,默认为Package权限,包内的class都可以访问。但是这还不够准确。确切的说,只有由同一个ClassLoader装载的class才具有以上的Package权限。比如Bootstrap classloader装载了java.lang.String,AppClassLoader装载了我们自己写的java.lang.TestPackage,它们不能互相访问对方具有Package权限的方法。这样就阻止了恶意代码访问核心类的Package权限方法。

 

五、关于两个ClassLoader载入同一个类

注意:由两个不同的ClassLoader载入的同一个类,其是不同类型的,因此如果进行赋值会报ClassCastException

示例:

 

[java] view plaincopy
 
  1. package Test;  
  2. import java.io.*;  
  3. import java.util.*;  
  4. public class TestClassLoader extends ClassLoader {  
  5.       
  6.     protected Map<String, Class> cache = new HashMap<String, Class>();  
  7.       
  8.     public TestClassLoader() {  
  9.         super();  
  10.     }  
  11.       
  12.     protected Class<?> findClass(String name) throws ClassNotFoundException {  
  13.         if (cache.get(name) != null) {  
  14.             return cache.get(name);  
  15.         }  
  16.         try {  
  17.             String tname = name.replace('.', '/');  
  18.             File file = new File("c://" + tname + ".class");  
  19.             FileInputStream in = new FileInputStream(file);  
  20.             BufferedInputStream bufIn = new BufferedInputStream(in);  
  21.             ByteArrayOutputStream byteOut = new ByteArrayOutputStream();  
  22.             BufferedOutputStream bufOut = new BufferedOutputStream(byteOut);  
  23.             byte[] buffer = new byte[4096];  
  24.             int len = -1;  
  25.             while((len = bufIn.read(buffer)) != -1) {  
  26.                 bufOut.write(buffer, 0, len);  
  27.             }  
  28.             bufOut.flush();  
  29.             byteOut.flush();  
  30.             byte[] data = byteOut.toByteArray();  
  31.             Class cls = defineClass(name, data, 0, data.length);  
  32.             cache.put(name, cls);  
  33.             return cls;  
  34.         } catch (IOException e) {  
  35.             e.printStackTrace();  
  36.         }  
  37.         return null;  
  38.     }  
  39. }  
  40. class TestLoaderA {  
  41.     public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {  
  42.         TestClassLoader loader = new TestClassLoader();  
  43.         System.out.println(TestAImp1.class.getClassLoader());  
  44.         System.out.println(loader.findClass("Test.TestAImp1").getClassLoader());  
  45.         TestAImp1 test = (TestAImp1)loader.findClass("Test.TestAImp1").newInstance();  
  46.         test.test();  
  47.           
  48.     }  
  49. }  
  50. public class TestAImp1 extends AbstractTestA {  
  51.     public void test() {  
  52.         System.out.println(Thread.currentThread().toString());  
  53.         System.out.println("Test");  
  54.     }  
  55. }  

 

 运行以上代码结果:

sun.misc.Launcher$AppClassLoader@197d257 
Test.TestClassLoader@1b90b39 
Exception in thread "main" java.lang.ClassCastException: Test.TestAImp1
 at Test.TestLoaderA.main(TestClassLoader.java:84)

posted on 2015-03-10 10:34  帅胡  阅读(248)  评论(0编辑  收藏  举报

导航