ClassLoader 加载机制

1. Java Class Loading Mechanism

首先当编译一个Java文件时,编译器就会在生成的字节码中内置一个public,static,final的class字段,该字段属于java.lang.Class类型,该class字段使用点来访问,所以可以有:
java.lang.Class clazz = MyClass.class

当class被JVM加载,就不再加载相同的class。class在JVM中通过(ClassLoader,Package,ClassName)来唯一决定。ClassLoader指定了一个class的scope,这意味着如果两个相同的包下面的class被不同的ClassLoader加载,它们是不一样的,并且不是type-compatible的。

JVM中所有的ClassLoader(bootstrap ClassLoader除外)都是直接或间接继承于java.lang.ClassLoader抽象类,并且人为逻辑上指定了parent-child关系,实现上child不一定继承于parent,我们也可以通过继承它来实现自己的ClassLoader。

JVM ClassLoder架构,从上到下依次为parent-child关系:
  • Bootstrap ClassLoader - 启动类加载器,主要负责加载核心Java类如java.lang.Object和其他运行时所需class,位于JRE/lib目录下或-Xbootclasspath指定的目录。我们不知道过多的关于Bootstrap ClassLoader的细节,因为它是一个native的实现,不是Java实现,所以不同JVMs的Bootstrap ClassLoader的行为也不尽相同。调用java.lang.String.getClassLoder() 返回null。
  • sun.misc.ExtClassLoader - 扩展类加载器,负责加载JRE/lib/ext目录及-Djava.ext.dirs指定目录。
  • sun.misc.AppClassLoader - 应用类加载器,负责加载java.class.path目录
  • 另外,还有一些其他的ClassLoader如:java.net.URLClassLoader,java.security.SecureClassLoader,java.rmi.server.RMIClassLoader,sun.applet.AppletClassLoader
  • 用户还可以自己继承java.lang.ClassLoader来实现自己的ClassLoader,用来动态加载class文件。
ClassLoader特性
  • 每个ClassLoader维护一份自己的命名空间,同一个ClassLoader命名空间不能加载两个同名的类。
  • 为实现Java安全沙箱模型,默认采用parent-child加载链结构,除Bootstrap ClassLoader没有parent外,每个ClassLoader都有一个逻辑上的parent,就是加载这个ClassLoader的ClassLoader,因为ClassLoader本身也是一个类,直接或间接的继承java.lang.ClassLoader抽象类。
java.lang.Thread中包含一个public的方法public ClassLoader getContextClassLoader(),它返回某一线程相关的ClassLoader,该ClassLoader是线程的创建者提供的用来加载线程中运行的classes和资源的。如果没有显式的设置其ClassLoader,默认是parent线程的Context ClassLoader。Java默认的线程上下文加载器是AppClassLoader。

ClassLoader工作原理:

了解ClassLoader工作原理,先来看一个ClassLoader类简化版的loadClass()方法源码
 1 protected Class<?> loadClass(String name, boolean resolve)
 2         throws ClassNotFoundException
 3     {
 4         synchronized (getClassLoadingLock(name)) {
 5             // First, check if the class has already been loaded
 6             Class c = findLoadedClass(name);
 7             if (c == null) {
 8                 long t0 = System.nanoTime();
 9                 try {
10                     if (parent != null) {
11                         c = parent.loadClass(name, false);
12                     } else {
13                         c = findBootstrapClassOrNull(name);
14                     }
15                 } catch (ClassNotFoundException e) {
16                     // ClassNotFoundException thrown if class not found
17                     // from the non-null parent class loader
18                 }
19 
20                 if (c == null) {
21                     // If still not found, then invoke findClass in order
22                     // to find the class.
24                     c = findClass(name);
25                 }
26             }
27             if (resolve) {
28                 resolveClass(c);
29             }
30             return c;
31         }
32     }

首先查看该class是否已被加载,如果已被加载则直接返回,否则调用parent的loadClass来加载,如果parent是null代表是Bootstrap ClassLoader,则有Bootstrap ClassLoader来加载,如果都未加载成功,最后由该ClassLoader自己加载。这种parent-child委派模型,保证了恶意的替换Java核心类不会发生,因为如果定义了一个恶意java.lang.String,它首先会被JVM的Bootstrap ClassLoader加载自己JRE/lib下的,而不会加载恶意的。另外,Java允许同一package下的类可以访问受保护成员的访问权限,如定义一个java.lang.Bad,但是因为java.lang.String由Bootstrap ClassLoader加载而java.lang.Bad由AppClassLoader加载,不是同一ClassLoader加载,仍不能访问。

2. Hotswap - 热部署

即不重启JVM,直接替换class。因为ClassLoader特性,同一个ClassLoader命名空间不能加载两个同名的类,所以在不重启JVM的情况下,只能通过新的ClassLoader来重新load新的class。

 1  public static void main(String[] args) throws InterruptedException, MalformedURLException {
 2         IExample oldExample = new Example();
 3         oldExample.plus();
 4         System.out.println(oldExample.getCount());
 5 
 6         Hotswap hotswap = new Hotswap();
 7         while (true) {
 8             IExample newExample = hotswap.swap(oldExample);
 9             String message = newExample.message();
10             int count = newExample.plus();
11             System.out.println(message.concat(" : " + count));
12             oldExample = newExample;
13             Thread.sleep(5000);
14         }
15     }
16 
利用hotswap替换就的Example,每5秒钟轮询一次,swap方法实现如下:
 1  private IExample swap(IExample old) {
 2         try {
 3             String sourceFile = srcPath().concat("Example.java");
 4             if (isChanged(sourceFile)) {
 5                 comiple(sourceFile, classPath());
 6                 MyClassLoader classLoader = new MyClassLoader(new URL[]{new URL("file:"+classPath())});
 7                 Class<?> clazz = classLoader.loadClass("Example");
 8                 System.out.println(IExample.class.getClassLoader());
 9                 IExample exampleInstance = ((IExample) clazz.newInstance()).copy(old);
10                 System.out.println(exampleInstance.getClass().getClassLoader());
11                 return exampleInstance;
12             }
13         } catch ...
24         return old;
25     }
这里必须将exampleInstance转型为IExample接口而不是Exmaple,否则会抛出ClassCastExecption,这是因为swap方法所在类Hotswap是有AppClassLoader加载的,而且加载Hotswap的同时会加载该类引用的Exmaple的symbol link,而Example是MyClassLoader加载的,不同的ClassLoader加载的类之间直接用会抛出ClassCastException, 在本例中ClassLoader实现如下:
 1 public class MyClassLoader extends URLClassLoader {
 2 
 3     public MyClassLoader(URL[] urls) {
 4         super(urls);
 5     }
 6 
 7     @Override
 8     public Class<?> loadClass(String name) throws ClassNotFoundException {
 9         if ("Example".equals(name)) {
10             return findClass(name);
11         }
12         return super.loadClass(name);
13     }
14 }
而对IExample我们还是调用super的loadClass方法,该方法实现仍是JVM的parent-child委派方式,因此最终由AppClassLoader加载,加载Hotswap时加载的symbol link也是由AppClassLoader加载的,因此能够成功。

此外再热部署时,被替换的类的所有引用及状态都要迁移到新的类上,本例中只是很简单的调用copy函数迁移了count的状态。

Tomcat的jsp热部署机制就是基于ClassLoader实现的,对于其类的热部署机制是通过修改内存中的class字节码实现的。

Resource:
Reloading Java Classes 101: Objects, Classes and ClassLoaders
Internals of Java Class Loading

posted on 2012-09-08 17:58 *** 阅读(615) 评论(0)  编辑  收藏


只有注册用户登录后才能发表评论。


网站导航:
 
<2012年9月>
2627282930311
2345678
9101112131415
16171819202122
23242526272829
30123456

导航

统计

常用链接

留言簿

随笔档案

搜索

最新评论

阅读排行榜

评论排行榜