java中的ClassLoader:
jdk中默认的classloader:程序打印一下可以看到
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
AppClassLoader会加载jdk的核心库,而ExtClassLoader顾名思义会加载ext的lib,如${JAVA_HOME}
/lib/ext
对双亲委派模式的理解:
所谓双亲委派,首先要有一个概念,这是对于官方jdk的默认classloader而言的,如果是使用自己的jvm,或者单单从classloader层面来讲,classloader不应该包含如何的加载策略,或者说我们可以自定义如何的加载策略。
双亲委派概念:双亲委派是指,当需要一个class时,当前环境的classloader调用loadClass首先查看findLoadedClass,如果没有找到,调用父classloader的loadClass,如果还没有找到才调用自己的findClass在当前classloader下查找加载class,如果还没有找到则会抛出ClassNotFoundException。也就是先从自己的缓存中找,找不到,则从父亲那里找,还找不到才自己尝试加载。这样可以避免类的重复加载,也就是无论在哪个classloader中查找一个相同的Class(如com.example.TestClass),该class的加载只会出现在最先可以找到该类的classloader下(最高层级)。
默认官方java的双亲委派机制是在ClassLoader类下的loadClass实现的。如果想要实现更复杂的加载策略,可以重载该方法。比如我们想从所有该classloader的子classloader下查找class,则可以保存该classloader的所有子classloader,在classloader中直接调用子类的findclass方法,然后在子classloader中重新实现,先调用findLoadedClass,如果找不到则调用super.findClass。
当然我们还可以实现更复杂的加载策略,比如我们把所有jar包保存在网络甚至分布式文件系统上,如果效率运行,我们可以在找不到类时动态从网络下载相应jar包,加载jar包,最后加载该class。也可以对class,jar进行自定义的加密,在加载jar包、class时解密该类。
关于class的动态热替换:
由于classloader加载完class后,无法卸载,想要加载同一个类的新版本(更高版本,拥有新功能或bug修复),理论上有两种方法:
一、直接修改类的字节码,对类名加上版本信息,但这存在一个问题,版本信息可能很难管理,我们要修改每一个loadClass的名字来保证其可以访问到最新的class,class的cache也要重写来卸载过时的类。而且一些已经在运行的代码,可能会出错。
二、实现一个新的类,java官网有对classloader的资源何时释放做说明:官网说一个classloader的资源会在该classloader的应用变得不可达时被释放。也就是classloader和其他javaclass的释放机制是一样的。这样,在加载新的版本的时候,我们可以创建一个新的classloader加载新的class替换原来的classlaoder来实现class的动态更新。
关于jar包的动态热替换:
jar包的热替换和class基本一样,但如果使用相同的代码来加载新版本的jar包时,从新new对象时,可能会发现出现的还是原jar包的类,并没有更新。热替换jar包时,我们不仅要替换classlaoder,还要关闭原jar的连接JarURLConnection。
一个例子:
这里实现一个新的加载策略:有两种classLoader:MainClassLaoder InnerClassLoader其中InnerClassLoader的parent是MainClassLaoder,每一个InnerClassLoader都只加载一个jar包,在MainClassLoader中loadClass时先会从所有子classLoader查找,找不到再从parent找。
package com.jsen.test;
import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.util.List;
/**
* @author jsen
*/
public class MainClassLoader extends URLClassLoader {
private static final Logger logger = LoggerFactory.getLogger(MainClassLoader.class);
private static final List<InnerClassLoader> classLoaderList = Lists.newArrayList();
public MainClassLoader(ClassLoader classLoader) {
super(new URL[] {}, classLoader);
}
/**
* 将指定的文件url添加到类加载器的classpath中去,并缓存jar connection,方便以后卸载jar
* 指定支持jar协议的URL
* @param url 一个可想类加载器的classpath中添加的文件url
*/
public void addFile(URL url, String md5) {
logger.debug("add jar with md5:" + md5);
InnerClassLoader innerClassLoader = new InnerClassLoader(url, md5, this);
classLoaderList.add(innerClassLoader);
}
public void del(String md5) {
logger.debug("del jar with md5:" + md5);
logger.debug(classLoaderList.size() + "");
for (int i = 0; i < classLoaderList.size(); i++) {
InnerClassLoader innerClassLoader = classLoaderList.get(i);
if (md5.equals(innerClassLoader.path)) {
innerClassLoader.delJar();
classLoaderList.remove(i);
break;
}
}
logger.debug(classLoaderList.size() + "");
// maps.remove(url.getPath());
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class c = null;
for (InnerClassLoader innerClassLoader : classLoaderList) {
c = innerClassLoader.findClass(name);
if (c != null) {
break;
}
}
if (c == null) {
c = super.loadClass(name, resolve);
}
if (resolve) {
resolveClass(c);
}
return c;
}
private static class InnerClassLoader extends URLClassLoader {
private String path;
private JarURLConnection jarURLConnection = null;
InnerClassLoader(URL url, String md5, MainClassLoader parent) {
super(new URL[] {}, parent);
this.path = md5;
try {
URLConnection uc = url.openConnection();
if (uc instanceof JarURLConnection) {
jarURLConnection = (JarURLConnection) uc;
jarURLConnection.getManifest();
}
} catch (IOException e) {
e.printStackTrace();
}
addURL(url);
}
@Override
protected Class<?> findClass(String name) {
Class<?> clazz = findLoadedClass(name);
try {
if (clazz == null) {
clazz = super.findClass(name);
}
} catch (ClassNotFoundException e) {
// e.printStackTrace();
}
return clazz;
}
void delJar() {
if (jarURLConnection != null) {
logger.debug("close jar url connection");
try {
jarURLConnection.getJarFile().close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}