首先当编译一个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: