[JVM-6]类加载器

定义

前面说过加载阶段是一个可以让设计人员高度自控的模块,因为类文件的源头可以是多种多样的,代码生成、反射生成或从网络中生成等。因此类加载器作为对这些文件的处理就显得尤为重要。

但类加载器的功能不仅如此,其还有一个重要的功能就是和一个类的全限定名唯一确定一个类。通俗来说,要说两个类是相同的,不仅其全限定名要一样,其对应的类加载器也必须相同,才能说明两个类是相等的。

正因为类加载器的功能角色如此重要,因此虚拟机对其的实现规范也十分重视。在Java虚拟机中,对其的实现模型是双亲委派模型

模型

双亲委派模型

双亲委派模型的主要执行过程示意图如上所示,其分为启动类加载器(Bootstrap Class-loader),拓展类加载器(Extension Class-loader),应用程序类加载(Application Class-loader)。

其中启动类加载器主要负责加载 JRE 的核心类库,如 JRE 目录下的 rt.jar。但其实根据《深入分析 Java Web 技术内幕》上所说,启动类加载器并不严格符合双亲委派模型,因为Bootstrap Class-loader 并不属于 JVM 的类等级层次。Bootstrap Class-loader 是没有子类的,Extension Class-loader 也是没有父类的。不过在这里我们并不深究,只要知道有这一点就可以了。

Extension Class-loader 主要负责加载 JRE 拓展目录 ext 下的类。

Application Class-loader 主要负责用户类路径(Class-path)下的类,这个类加载器是使用的最多的,因为大大多数情况下,一般开发者并没有实现自定义的类加载器,那么 JVM 就会使用这个来加载类大部分类。

执行过程

双亲委派模型执行过程

上图就是双亲委派模型的执行过程,当类开始加载的时候,先检查是否已经被加载过,如果没有被加载过,则调用父类的加载方法,如果父类加载失败,抛出异常,则调用自身的 findClass() 方法进行加载。

JDK 中加载过程的源码分析:

protected Class<?> loadClass(String name,boolean resolve) throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // First,check if the class has already been loaded // 如果加载过了,就不要加载直接返回 Class c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { // 判断是否有父加载器 if (parent != null) { // 有父加载器则调用父加载器加载 c = parent.loadClass(name, false); } else { // 无父亲就调用 bootstarp 加载器来加载 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } // 父加载器和 bootstarp 加载器都没有找到指定类,调用当前类的 findClass() 来完成类加载 // 因此,自定义类加载器,就是重写 findClass() 方法 if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }

从源码中,我们可以看到其实符合规范要求的 双亲委派模型 的。而当我们要自定义一个类加载器的时候就是通过重写 findClass() 来实现的。

自定义类加载器

/**
 * 1. 自定义类加载器通过集成ClassLoader来实现,主要通过重写findClass方法
 * 2. findClass方法首先通过自定义的loadByte()方法将Class文件转换成byte[]字节流
 * 3. 然后通过defineClass()方法将其转换为Class对象
 */
public class SelfClassLoader extends ClassLoader { private String classPath; public SelfClassLoader(String classPath) { this.classPath = classPath; } /** * 通过 difineClass,将一个字节数组转换为Class 对象 * @param name * @return * @throws ClassNotFoundException */ @Override protected Class<?> findClass(String name) throws ClassNotFoundException { try { byte[] data = loadByte(name); return defineClass(name, data, 0, data.length); } catch (Exception e) { e.printStackTrace(); throw new ClassNotFoundException(); } } /** * 根据路径将指定的文件读取为byte 流 * @param name * @return * @throws IOException */ private byte[] loadByte(String name) throws IOException { name = name.replaceAll("\\.", "/"); FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class"); int len = fis.available(); byte[] data = new byte[len]; fis.read(data); fis.close(); return data; } }

另一个种实现自定义类加载器的方法:

/**
 * 1. 加载指定packageName下的类
 * 2. 用自定义类加载器进行加载,如果加载失败,再交给父加载器进行加载
 */
public class UrlSelfClassloader extends URLClassLoader { private String packageName = ""; public UrlSelfClassloader(URL[] urls, ClassLoader parent) { super(urls, parent); } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { Class<?> aClass = findLoadedClass(name); if (Objects.nonNull(aClass)){ return aClass; } if (!packageName.startsWith(name)){ return super.loadClass(name); }else { return findClass(name); } } }

如何使用自定义的类加载器

public static void main(String args[]) throws Exception { MyClassLoader classLoader = new MyClassLoader(""); Class clazz = classLoader.loadClass(""); Object obj = clazz.newInstance(); Method helloMethod = clazz.getDeclaredMethod("hello", null); helloMethod.invoke(obj, null); }


猜你喜欢

转载自www.cnblogs.com/Joy-Hu/p/11102547.html
今日推荐