ClassLoader类加载器
ClassLoader是Java的核心组件,所有的Class都是由ClassLoader进行加载的。ClassLoader通过各种方式,将CLass信息的二进制流读入系统,然后交给JVM进行连接、初始化等操作。
比较两个类是否“相等”
比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义。这里所指的相等,包括equals()、isAssignableFrom()、isInstance()、instanceof等。如下所示:
/**
* 类加载器与instanceof关键字演示
*
* @author xuefeihu
*
*/
public class ClassLoaderTest {
public static void main(String[] args) throws Exception {
ClassLoader myLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream is = getClass().getResourceAsStream(fileName);
if(is == null) {
return super.loadClass(name);
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (Exception e) {
throw new ClassNotFoundException(name);
}
}
};
Object obj = myLoader.loadClass("com.moguhu.understanding.jvm.chapter7.section7_4_loader.ClassLoaderTest").newInstance();
System.out.println(obj.getClass());
System.out.println(obj instanceof com.moguhu.understanding.jvm.chapter7.section7_4_loader.ClassLoaderTest);
}
}
运行结果:
class com.moguhu.understanding.jvm.chapter7.section7_4_loader.ClassLoaderTest
false
产生上述的运行结果的原因是:obj是用户自定义类加载器加载的,instanceof后面的com.moguhu.***.ClassLoaderTest是系统应用程序类加载器加载的。
ClassLoader类
如果我们需要自定义ClassLoader,那么JDK中提供了java.lang.ClassLoader抽象类,我们可以通过继承来实现自定义ClassLoader。其主要方法如下:
// 给定一个类名,加载这个类,返回代表这个类的Class对象实例;如果找不到,则抛出异常
public Class<?> loadClass(String name) throws ClassNotFoundException;
// 根据给定的字节流b,定义一个名称为name的类,off和len参数表示Class信息在byte[]中的位置和长度。
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
throws ClassFormatError;
// 查找一个类(是自定义时的重要拓展点),如果想改变类加载的形式,可以重载
protected Class<?> findClass(String name) throws ClassNotFoundException;
// 它会去寻找已经加载的类,不可被重载
protected final Class<?> findLoadedClass(String name);
双亲委派模式
双亲委派模式的过程如下:如果一个类加载器收到了类加载请求,他首先不会自己去加载,而是把请求为拍给父类加载器去完成。一层一层的循环下去,直到顶层类加载器。当父类返回无法完成时,子类加载器才会尝试自己加载。如下图所示:
下面介绍一下JDK中提供的几个系统类加载器:
启动类加载器(Bootstrap ClassLoader):它负责加载放在/lib目录中的(按照文件名识别,如果lib下面存放了其他jar文件,则不予加载),或者被-Xbootclasspath参数指定的路径。
拓展类加载器(Extension ClassLoader):它由sun.misc.Launcher$ExtClassLoader实现,负责加载/lib/ext目录中,或者被java.ext.dirs系统变量指定的路径中的类库,开发者可以直接使用。
应用程序类加载器(Application ClassLoader):它由sun.misc.Launcher$AppClassLoader实现,它负责加载用户ClassPath上指定的类库。如果应用程序中没有自定义的类加载器,一般情况下它就是默认的加载器,还有另外一个名字叫:系统类加载器。
突破双亲模式
双亲委派模式是JVM的默认行为,事实上可以通过自定义ClassLoader来改变其行为。比如Tomcat和OSGI都有各自独特的类加载顺序。下面看一个自定义ClassLoader的Demo:
继承ClassLoader
/**
* 自定义ClassLoader,可以从指定Path加载
*
* @author xuefeihu
*
*/
public class PathClassLoader extends ClassLoader {
private static final String packageName = "com.moguhu.deep.javaweb.chapter6.section6_6_myclassloader";
private String classPath;
public PathClassLoader(String classPath) {
this.classPath = classPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
if(packageName.startsWith(name)) {
byte[] classData = getData(name);
if(classData == null) {
throw new ClassNotFoundException();
} else {
return defineClass(name, classData, 0, classData.length);
}
} else {
return super.findClass(name);
}
}
private byte[] getData(String className) {
String path = classPath + File.separatorChar + className.replace('.', File.separatorChar) + ".class";
try {
InputStream is = new FileInputStream(path);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
byte[] buffer = new byte[2048];
int num = 0;
while((num = is.read(buffer)) != -1) {
stream.write(buffer, 0, num);
}
return stream.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
// TODO
}
return null;
}
}
从上面代码可以看出,classPath目录下的class文件使用自定义的ClassLoader加载,其他的类还是使用父类加载器去加载。
继承URLClassLoader
URLClassLoader可以设定自定义的URL来加载Class文件,如下所示:
public class URLPathClassLoader extends URLClassLoader {
private String packageName = "com.moguhu.deep.javaweb.chapter6.section6_6_myclassloader";
public URLPathClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> aClass = findLoadedClass(name);
if(aClass != null) {
return aClass;
}
if(!packageName.startsWith(name)) {
return super.loadClass(name);
} else {
return findClass(name);
}
}
}
热替换实现思路
热替换是指在程序的运行过程中,不停止服务,只通过替换程序文件来修改程序的行为。大部分的脚本语言都是天生支持热替换的,如:PHP。而对于Java来说,实现热替换的思路如下所示:
在实现时,首先需要自定义ClassLoader(如下代码所示),他可以在给定的目录下查找目标类,主要的实现思路是重载findClass()方法。
自定义ClassLoader
/**
* 自定义ClassLoader
*
* @author xuefeihu
*
*/
public class MyClassLoader extends ClassLoader {
private String fileName;
public MyClassLoader(String fileName) {
this.fileName = fileName;
}
@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
Class<?> clazz = this.findLoadedClass(className);
if(null == clazz) {
try {
String classFile = getClassFile(className);
FileInputStream fis = new FileInputStream(classFile);
FileChannel fileC = fis.getChannel();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
WritableByteChannel outC = Channels.newChannel(baos);
ByteBuffer buffer = ByteBuffer.allocate(1024);
while(true) {
int i = fileC.read(buffer);
if(i == 0 || i == -1) {
break;
}
buffer.flip();
outC.write(buffer);
buffer.clear();
}
fis.close();
byte[] bytes = baos.toByteArray();
clazz = defineClass(className, bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
}
}
return clazz;
}
/**
* 获取Class文件路径
*/
private String getClassFile(String className) {
// TODO Auto-generated method stub
return null;
}
}
需要热替换的类
/**
* 需要热替换的类
*
* @author xuefeihu
*
*/
public class DemoA {
public void hot(){
System.out.println("OldDemoA");
}
}
测试类
public class DoopRun {
public static void main(String[] args) {
while(true) {
try {
MyClassLoader loader = new MyClassLoader("/Users/xuefeihu");
Class<?> cls = loader.loadClass("com.moguhu.combat.jvm.chapter10.section10_2_classloader.DemoA");
Object demo = cls.newInstance();
Method m = demo.getClass().getMethod("hot", new Class[]{});
m.invoke(demo, new Object[]{});
Thread.sleep(10000);
} catch (Exception e) {
System.out.println("not find");
try {
Thread.sleep(10000);
} catch (Exception e2) {
}
}
}
}
}
上述代码运行时,会不断的打出“OldDemoA”。当我们更改DemoA的打印内容(如:“NewDemoA”),并且重新编译并替换原先.class文件。程序将会打印“NewDemoA”。
Class对象在JVM中只有一份,理论上可以直接替换,然后更新Java栈中所有对原对象的引用关系。看起来被替换了,但是仍然不可行,因为其违反了JVM的设计原则。对象引用关系只有创建者持有和使用,JVM不可以柑橘对象的引用关系。
造成不能动态替换类对象的关键是:对象的状态被保存了,并且被其他对象引用了。一个简单的方法就是不保存对象的状态,对象创建使用后就被释放掉,当修改后就是新的了。这种思想比较典型的例子就是JSP,其他解释型语言亦是如此。
参考:《深入理解Java虚拟机》、《实战Java虚拟机》、《深入分析Java Web》
上一篇: ClassLoader类加载器
下一篇: java类加载器ClassLoader