java类加载机制

类加载

java类加载器

Java中的类加载器,有启动类加载器(Bootstrap Classloader)、扩展类加载器(Launcher(ExtClassLoader)、应用程序类加载器(Launcher)AppClassLoader),用户还可以实现自定义的类加载器。

  • Bootstrap类加载器负责加载rt.jar中的JDK类文件,它是所有类加载器的父加载器。默认是负责加载$JRE_HOME/lib目录下面的类,也可以通过JVM参数-Xbootclasspath来指定需要加载类的路径.

  • Extension将加载类的请求先委托给它的父加载器,也就是Bootstrap,如果没有成功加载的话,再从jre/lib/ext目录下或者java.ext.dirs系统属性定义的目录下加载类。Extension加载器由sun.misc.Launcher$ExtClassLoader实现。

  • Application类加载器, 它负责从classpath环境变量中加载某些应用相关的类,classpath环境变量通常由-classpath或-cp命令行选项来定义,或者是JAR中的Manifest的classpath属性。Application类加载器是Extension类加载器的子加载器。通过sun.misc.Launcher$AppClassLoader实现。

Bootstrap类加载器是由C来写的,其他的类加载器都是通过java.lang.ClassLoader来实现的。

jvm启动类加载器加载过程:

  1. 根据JVM内存配置要求,为JVM申请特定大小的内存空间;
  2. 创建一个引导类加载器(Bootstrap Classloader)实例,初步加载系统类到内存方法区区域中;
  3. 创建JVM 启动器实例 Launcher,并取得类加载器ClassLoader(在Launcher的内部,其定义了两个类加载器,分别是sun.misc.Launcher.ExtClassLoader和sun.misc.Launcher.AppClassLoader);
  4. 使用上述获取的ClassLoader实例加载我们定义的Main类;
  5. 加载完成时候JVM会执行Main类的main方法入口,执行Main类的main方法;
  6. 结束,java程序运行结束,JVM销毁。

ClassLoader源码解析

1. loadClass

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); // 检查该类是否已经被JVM加载过,若加载过则直接返回
        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;
    }
}

由loadClass源码可知, loadCLass加载步骤为:

  1. 先从jvm中找, 若之前已经被加载则返回对应Class, 若为之前未被加载则走2
  2. 若此类加载器存在父类, 则交给父类加载, 若父类加载成功返回类对象, 加载失败返回null则走4
  3. 若此类加载器不存在父类, 则交给Bootstrap Classloader加载, 若加载成功返回类对象, 加载失败返回null则走4
  4. 上述加载失败, 调用当前类加载器的findClass加载, 加载成功返回类对象(加载失败后...).

由loadClass的逻辑已经能够清楚的看出双亲委派原则的实现逻辑:

首先都是交给父类去加载,如果父类无法加载再交给子类去完成,直到调用用户自定义的类加载器去加载,如果全部都无法加载,就会抛出ClassNotFoundException。

2. findClass

protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
}

CLassLoader中的findClass交由子类实现。

3. defineClass

通过字节码byte[] 获取字节码对象。

DefineClass在Java反序列化漏洞当中的利用

protected final Class<?> defineClass(String name, byte[] b, int off, int len)
        throws ClassFormatError {
        return defineClass(name, b, off, len, null);
    }
 protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                         ProtectionDomain protectionDomain)
        throws ClassFormatError{
        protectionDomain = preDefineClass(name, protectionDomain);

        Class c = null;
        String source = defineClassSourceLocation(protectionDomain);

        try {
            c = defineClass1(name, b, off, len, protectionDomain, source);
        } catch (ClassFormatError cfe) {
            c = defineTransformedClass(name, b, off, len, protectionDomain, cfe,
                                       source);
        }

        postDefineClass(c, protectionDomain);
        return c;
    }

参考
JVM类加载器机制与类加载过程

DefineClass在Java反序列化漏洞当中的利用

原文地址:https://www.cnblogs.com/jxkun/p/9354708.html