Java ClassLaoder小解

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();
                }
            }
        }
    }
}

猜你喜欢

转载自blog.csdn.net/jsenht/article/details/80492888