类加载器

Posted 0xweng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了类加载器相关的知识,希望对你有一定的参考价值。

类加载器(Class Loader)

类加载器负责在运行时将Java类动态加载到JVM(Java虚拟机)。此外,它们也是JRE(Java Runtime Environment)的一部分。因此,由于有了类加载器,JVM不需要知道底层文件或文件系统就可以运行Java程序。此外,这些Java类不会一次全部加载到内存中,而是在应用程序需要时加载。这就是类装入器的用武之地。它们负责将类加载到内存中。

内置类加载器的类型(Types of Built-in Class Loaders)

bootstrap 启动加载器

A bootstrap or primordial class loader is the parent of all the others.

它主要负责加载JDK内部类,通常是rt.jar和$ JAVA_HOME / jre / lib目录中的其他核心库。bootstrap是所以Java类加载器的爸爸,正常的类用java,lang.classloader来加载,那么classloader就是由bootstrap类加载器来加载的,所以bootstrap并不是用Java写的,不同平台上的bootstrap加载器肯定是不同的。

application/system 系统加载器

An application or system class loader loads our own files in the classpath.

负责加载Java application level的类进入到JVM中。它加载在classpath环境变量、-classpath或-cp命令行选项中找到的文件。而且,它是Extensions类加载器的一个子类。

extension 拓展类加载器

Extension class loaders load classes that are an extension of the standard core Java classes.

扩展类装入器是引导类装入器的一个子类,负责装入标准的核心Java类的扩展,以便所有在平台上运行的应用程序都可以使用它。扩展类加载器从JDK扩展目录(通常是$ JAVA_HOME / lib / ext目录)或java.ext.dirs系统属性中提到的任何其他目录加载。

类加载器的工作原理

类加载器是JRE的一部分。当JVM请求一个类的时候,类加载器会尝试定位类,并使用完全限定的类名将类定义加载到运行时中。
java.lang.ClassLoader.loadClass() 负责加载类定义加载到运行时。它尝试基于完全限定的名称加载类。

如果尚未加载该类,则它将请求委托给父类加载器。 此过程是递归发生的。如果父类也没有找到该类,则其子类调用java.net.URLClassLoader.findClass() 来查找文件系统本身中的类,如果最后一个子类也没有找到该类则会抛出java.lang.NoClassDefFoundErrorjava.lang.ClassNotFoundException.

如果我们从调用java.lang.Class.forName()开始经历一系列事件,我们可以理解它首先尝试通过父类装入器装入类,然后通过java.net.URLClassLoader.findClass()来查找类本身。当它仍然没有找到该类时,它会抛出一个ClassNotFoundException

类加载器的三个特征

委托模型(Delegation Model)

类装入器遵循委托模型,在请求查找类或资源时,类装入器实例将把类或资源的搜索委托给父类装入器。假设我们有一个将应用程序类加载到JVM中的请求。系统类装入器首先将类的装入委托给父扩展类装入器,父扩展类装入器又将类装入委托给引导类装入器。只有当引导程序和扩展类装入器装入类失败时,系统类装入器才尝试装入类本身。

唯一类(Unique Classes)

作为委托模型的结果,很容易确保惟一的类,因为我们总是试图向上委托。如果父类装入器无法找到该类,则只有在此情况下,当前实例才会尝试自己找到该类。

可视化(visibility)

此外,子类装入器对于其父类装入器装入的类是可见的。例如,系统类装入器装入的类可以看到扩展装入的类和引导类装入器装入的类,但反之则不行。为了说明这一点,如果类A是由应用程序类装入器装入的,而类B是由扩展类装入器装入的,那么就应用程序类装入器装入的其他类而言,A和B类都是可见的。尽管如此,在扩展类装入器a装入的其他类中,类B是惟一可见的类。

自定义类加载器

package com.j0nathan.classloader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;

public class CustomClassLoader extends ClassLoader {
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] b = loadClassFromFile(name);
        return defineClass(name, b, 0, b.length);
    }
    private byte[] loadClassFromFile(String fileName) {
        InputStream inputStream = getClass().getClassLoader().
                getResourceAsStream(fileName.replace(".", File.separator) + ".class");
        byte[] buffer;
        ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
        int nextValue = 0;
        try {
            while ((!((nextValue = inputStream.read()) != -1))) {
                byteStream.write(nextValue);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        buffer = byteStream.toByteArray();
        return buffer;
    }
}

深入理解 java.lang.ClassLoader

loadClass()

此方法负责加载给定名称参数的类。name参数引用完全限定的类名
Jva虚拟机调用loadClass()方法来将类引用解析为true。然而,并不总是需要解析一个类。如果只需要确定类是否存在,则resolve参数设置为false。
这个方法就是ClassLoader的入口

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 {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

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

该方法的默认实现按以下顺序搜索类
调用findLoadedClass(String)方法来查看类是否已经加载。
在父类装入器上调用loadClass(String)方法。
调用findClass(String)来查找该类

## defineClass() Method

protected final Class<?> defineClass(
  String name, byte[] b, int off, int len) throws ClassFormatError

此方法负责将字节数组转换为类的实例。在使用类之前,我们需要解决它。如果数据不包含有效的类,则抛出ClassFormatError。另外,我们不能覆盖这个方法,因为它被标记为final

findClass() Method

protected Class<?> findClass(
  String name) throws ClassNotFoundException

此方法查找具有完全限定名作为参数的类。我们需要在自定义类装入器实现中重写此方法,该实现遵循用于装入类的委托模型。另外,如果父类装入器找不到所请求的类,loadClass()将调用此方法。如果类装入器的父类没有找到该类,则默认实现将抛出ClassNotFoundException。

getParent() Method

public final ClassLoader getParent()

此方法返回委托的父类装入器。

## getResource() Method

public URL getResource(String name)

此方法尝试查找具有给定名称的资源。
它将首先委托给资源的父类加载器。如果父级为NULL,则搜索内置到虚拟机中的类加载器的路径。如果失败,则该方法将调用findResource(String)来查找资源。
指定为输入的资源名称可以是类路径的相对名称,也可以是绝对名称。
它返回一个用于读取资源的URL对象,如果找不到资源或调用者没有足够的权限返回资源,则返回NULL。
需要注意的是,Java从类路径加载资源。
Java中的资源加载被认为是与位置无关的,因为只要环境设置为查找资源,代码在哪里运行并不重要。

Context Classloaders

通常,上下文类装入器为J2SE中引入的类装入委托方案提供了一种替代方法。
JVM中的类装入器遵循分层模型,这样每个类装入器都有一个父类,引导类装入器除外。

然而,有时当JVM核心类需要动态加载应用程序开发人员提供的类或资源时,我们可能会遇到问题。例如,在JNDI中,核心功能是由rt.jar中的bootstrap类实现的。但是这些JNDI类可能会加载由独立供应商实现的JNDI提供程序(部署在应用程序类路径中)。这个场景需要bootstrap类装入器(父类装入器)来装入对应用程序装入器(子类装入器)可见的类。J2SE委托在这里不起作用,为了解决这个问题,我们需要找到类装入的替代方法。它可以使用线程上下文加载器来实现。

java.lang.Thread类有一个getContextClassLoader()方法,该方法返回特定线程的ContextClassLoader。ContextClassLoader是线程的创建者在加载资源和类时提供的。如果未设置该值,则默认为父线程的类装入器上下文。

Conclusion

以上就是关于ClassLoader的一个简短的笔记

以上是关于类加载器的主要内容,如果未能解决你的问题,请参考以下文章

活动(加载器 - 下载)+ 3 个片段(使用加载器 - 计算)

损坏的顶点和片段着色器

Honeycomb 中的片段加载微调器/对话框

用于在多个活动/片段中重用的全局加载器 (LoaderManager)

Android 逆向ART 脱壳 ( DexClassLoader 脱壳 | DexClassLoader 构造函数 | 参考 Dalvik 的 DexClassLoader 类加载流程 )(代码片段

Android 逆向ART 脱壳 ( DexClassLoader 脱壳 | DexClassLoader 构造函数 | 参考 Dalvik 的 DexClassLoader 类加载流程 )(代码片段