类加载器是java的一块较难啃的骨头,本人通过查看文档,和同事讨论,敲代码实验,有了一些心得,所以写下来备忘,也请各路大神批评指教。
我偏向认为java是一种编译+解释型语言,jvm首先会通过编译器把java文件编译成class中间文件,然后通过加载器把class文件加载成机器可识别的机器语言,然后jvm提供解释执行的环境,因此java是一种可跨平台执行的语言(一次编写,多处执行)。
1.关于A.class.getClassLoader()的疑问
我们应该都知道jvm启动时会预先加载一些基础类,主要是rt.jar。我们编写的类一般会在第一次被使用的加载进内存。于是在最开始了解classLoader的时候,我就出现这样一个疑问,如果A类所在的路径同时可以被App的两个孩子的类加载器来加载,那么如下代码中的loader是哪一个加载器呢?
package iteye;
public class Test {
public static void main(String[] args){
ClassLoader loader = A.class.getClassLoader();
}
}
因为A.class.getClassLoader()在被执行之前,A已经就被加载了,而且显然是被当前程序的类加载器AppClassLoader来加载。而如果Test这个类是由AppClassLoader的一个子加载器MyClassLoader1来加载,那么这时候loader是什么类加载器呢?答案还是AppClassLoader,因为委托机制,子加载器为优先委托给父加载器来加载。我们可以把A的class文件剪切到MyClassLoader1指定的路径,让AppClassLoader找不到,于是这个时候loader就是MyClassLoader1了。
2.Class.forName("iteye.A")能初始化static里面的代码
字节码的加载会涉及到如下几个阶段:装载-链接-验证-准备-解析-初始化。而类变量在字节码被加载的时候会被初始化,但是如下代码会打印"initail A!"吗?
package iteye;
public class A {
static{
System.out.println("initail A!");
}
}
package iteye;
public class Test {
public static void main(String[] args) throws ClassNotFoundException{
ClassLoader.getSystemClassLoader().loadClass("iteye.A");
}
}
答案是不会。因为字节码的加载是有阶段性的,这种加载类的方法不会执行初始化阶段,所以要打印出"initail A!",就应该用Class.forName("iteye.A");
3.为什么要用Thread.currentThread().setContextClassLoader()和Thread.currentThread().getContextClassLoader()
既然已经可以用程序类加载器和自定义加载器来加载类,为什么还要当前线程加载器呢?因为有的时候我们需要突破类加载器委托父类加载这套机制。由于子加载器会优先让父加载器来加载,而父加载器又不可见子加载器,如果此时有一个类在由父加载器加载,而这个类里面又有一些代码需要有子加载器来加载,这时候就需要用到Thread.currentThread().setContextClassLoader()和Thread.currentThread().getContextClassLoader()了。我们可以在需要被子加载器加载的类的当前线程中指定该子加载器,然后在被引用的并且需要被父加载器加载的程序中显示的得到该线程的类加载器,然后用这个加载器来加载类。
4.如何实现一个跨classLoader的单例
package zctPackage.classLoader; import java.lang.reflect.Method; import java.util.Date; public class SingletonClass extends Date{ private SingletonClass (){} private static SingletonClass INSTANCE = new SingletonClass (); private static final Date getInstance() { return INSTANCE; } private static Class getClass(String classname){ ClassLoader classLoader = ClassLoader.getSystemClassLoader(); System.out.println("system classloader: " + classLoader); try { return classLoader.loadClass("zctPackage.classLoader." + classname.substring(classname.lastIndexOf('.')+1)); } catch (ClassNotFoundException e) { e.printStackTrace(); } return null; } public static Date getInstance(String classname){ Date singleton = null; Class<?> clazz = null; ClassLoader classLoader = ClassLoader.getSystemClassLoader(); try { clazz = classLoader.loadClass("zctPackage.classLoader." + classname.substring(classname.lastIndexOf('.')+1)); } catch (ClassNotFoundException e1) { e1.printStackTrace(); } if(clazz != null){ try { Method staticMethod = clazz.getDeclaredMethod("getInstance"); staticMethod.setAccessible(true); singleton = (Date) staticMethod.invoke(clazz); } catch (Exception e) { e.printStackTrace(); } } return singleton; } }为什么要让SingletonClass继承Date? 如果此处singleton = (Date) staticMethod.invoke(clazz);改成singleton = (SingletonClass) staticMethod.invoke(clazz);会报ClassCastException。想想为什么,因为本例中,不管这个单例类开始由什么不同类加载器去加载,最终我始终用SystemClassLoader来加载,通过反射获取实例,这样就可以实现跨类加载器的单例模式。实际应用中,此单例类可能由不同的类加载器去加载,我们知道不同类加载器加载同一个类,class是不相等的。因此会报ClassCastException。而用Date来父类引用就没有问题,因为他们都会委托BootStrap类加载器去加载。
测试类:
package zctPackage.classLoader;
import java.lang.reflect.Method;
import java.util.Date;
public class SingletonTest {
/**
* @param args
*/
public static void main(String[] args) throws Exception {
ClassLoader curentLoader = Thread.currentThread().getContextClassLoader();
System.out.println(curentLoader);
System.out.println("-----------------------------------------");
//UndelegationClassLoader为自定义类加载器
ClassLoader loader1 = new UndelegationClassLoader("zctPackage.classLoader");
ClassLoader loader2 = new UndelegationClassLoader("zctPackage.classLoader");
System.out.println("loader1: " + loader1);
System.out.println("load the classloader's class: " + loader1.getClass().getClassLoader());
Class clazz1 = loader1.loadClass("SingletonClass");
Class clazz2 = loader2.loadClass("SingletonClass");
System.out.println(clazz1.getClassLoader());
System.out.println(clazz2.getClassLoader());
System.out.println("compare the clazz: " + clazz1.equals(clazz2));
Method method1 = clazz1.getMethod("getInstance", String.class);
Method method2 = clazz2.getMethod("getInstance", String.class);
Date s1 = (Date) method1.invoke(clazz1, "SingletonClass");
Date s2 = (Date) method2.invoke(clazz2, "SingletonClass");
System.out.println("-------------------------------------------------");
System.out.println("compare the instance: " + s1.equals(s2));
}
}
最后比较两个实例s1和s2是相等的。