栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

JVM -- 类加载子系统

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JVM -- 类加载子系统

类加载子系统
      • 类加载器
        • 启动类加载器BootstrapClassLoader
        • 扩展类加载器ExtensionClassLoader
        • 应用类加载器ApplicationClassLoader
        • 自定义类加载器xxxClassLoader
      • 类加载器加载的类在方法区的存储
      • 双亲委派(向上委派)
        • 类搜索过程
        • 打破双亲委派
        • 自定义类加载器
        • SPI机制
        • 线程上下文类加载器
        • 沙箱安全

类加载器

启动类加载器BootstrapClassLoader
  • 由C++编写,java程序获取不到为null,其他都是java编写
  • 获取启动类加载器加载路径:URL[] urLs = -Launcher.getBootstrapClassPath().getURLs();
  • 可以通过-Xbootclasspath指定

openJdk源码

  1. 通过JavaMain入口调用LoadMainClass
  2. 执行C++的LoadMainClass调用GetLauncherHelperClass,加载java的LauncherHellper
  3. 调用LauncherHellper的chechAndLoadMain
  4. 加载main函数所在的类,
  5. 然后启动扩展类加载器、应用类加载器也是在这个时候完成
int JNICALL
JavaMain(void * _args)
{
	......
    mainClass = LoadMainClass(env, mode, what);
    ......
}

static jclass
LoadMainClass(JNIEnv *env, int mode, char *name)
{
	......
    //加载java的sun/launcher/LauncherHelper
    jclass cls = GetLauncherHelperClass(env);
    NULL_CHECK0(cls);
    if (JLI_IsTraceLauncher()) {
        start = CounterGet();
    }
    NULL_CHECK0(mid = (*env)->GetStaticMethodID(env, cls,
                "checkAndLoadMain",
                "(ZILjava/lang/String;)Ljava/lang/Class;"));
	......
}

jclass
GetLauncherHelperClass(JNIEnv *env)
{
    if (helperClass == NULL) {
        NULL_CHECK0(helperClass = FindBootStrapClass(env,
                "sun/launcher/LauncherHelper"));
    }
    return helperClass;
}
扩展类加载器ExtensionClassLoader
  • 加载路径: URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader().getParent();
  • URL[] urls = urlClassLoader.getURLs();
  • 可以通过java.ext.dirs指定

OpenJdk源码

  1. 启动类加载器从C++调到java代码LauncherHelper.checkAndLoadMain
  2. 执行类加载scloader.loadClass(cn)时会先初始化ClassLoader
  3. 通过getSystemClassLoader初始化initSystemClassLoader(),调用Launcher
  4. Launcher.getLauncher()初始化Launcher, 创建扩展类加载器
private static final ClassLoader scloader = ClassLoader.getSystemClassLoader();

public static Class checkAndLoadMain(boolean printToStderr,int mode, String what) {
    ......
        //加载类
        mainClass = scloader.loadClass(cn);
    ......
    }

public static ClassLoader getSystemClassLoader() {
    initSystemClassLoader();
    if (scl == null) {
        return null;
    }
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkClassLoaderPermission(scl, Reflection.getCallerClass());
    }
    return scl;
}

private static synchronized void initSystemClassLoader() {
    if (!sclSet) {
        if (scl != null)
            throw new IllegalStateException("recursive invocation");
        // 加载 Launcher
        sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
        ......
        }
}

private static Launcher launcher = new Launcher();
public static Launcher getLauncher() {
    return launcher;
}

public Launcher() {
    ClassLoader extcl;
    try {
        //扩展类加载器
        extcl = ExtClassLoader.getExtClassLoader();
    } catch (IOException e) {
        throw new InternalError("Could not create extension class loader", e);
    }
    try {
        //应用类加载器(传入ext扩展类加载器作为父加载器)
        loader = AppClassLoader.getAppClassLoader(extcl);
    } catch (IOException e) {
        throw new InternalError("Could not create application class loader", e);
    }
    // 还要为原始线程设置上下文类加载器 (SPI原理,为了打破双亲委派)
    Thread.currentThread().setContextClassLoader(loader);
    ......
}

public static ExtClassLoader getExtClassLoader() throws IOException {
    final File[] dirs = getExtDirs();
    return AccessController.doPrivileged(
        new PrivilegedExceptionAction() {
            public ExtClassLoader run() throws IOException {
                int len = dirs.length;
                for (int i = 0; i < len; i++) {
                    MetaIndex.registerDirectory(dirs[i]);
                }
                //创建扩展类加载器
                return new ExtClassLoader(dirs);
            }
        });
}
应用类加载器ApplicationClassLoader
  • 默认加载用户程序的类加载器
  • 加载路径: String[] urls = System.getProperty(“java.class.path”).split(“:”);
  • 可以通过java.class.path指定

OpenJdk源码(扩展类加载器Launcher)

static class AppClassLoader extends URLClassLoader {

    static {
        ClassLoader.registerAsParallelCapable();
    }

    public static ClassLoader getAppClassLoader(final ClassLoader extcl)
        throws IOException
    {
        final String s = System.getProperty("java.class.path");
        final File[] path = (s == null) ? new File[0] : getClassPath(s);
        return AccessController.doPrivileged(
            new PrivilegedAction() {
                public AppClassLoader run() {
                    URL[] urls =
                        (s == null) ? new URL[0] : pathToURLs(path);
                    //创建启动类加载器,传入ext加载器为父类
                    return new AppClassLoader(urls, extcl);
                }
            });
}
自定义类加载器xxxClassLoader
  • 继承类java.lang.ClassLoader
  • 重写findClass/loadClass
类加载器加载的类在方法区的存储
  • 方法区按照类加载器进行分开存储,不同的类加载器有各自的方法区空间
  • 不同类加载器加载同一个类,存储位置是不同的
  • 同一个类加载器多次加载同一个文件,只加载一次
  • InstanceKlass会存储在方法区各自加载器的空间上
双亲委派(向上委派)

类搜索过程
  1. 如果一个类加载器收到了加载某个类的请求,则该类加载器并不会去加载该类,而是把这个请求委派给父类加载器,
  2. 每一个层次的类加载器都是如此,因此所有的类加载请求最终都会传送到顶端的启动类加载器;
  3. 只有当父类加载器在其搜索范围内无法找到所需的类,并将该结果反馈给子类加载器,子类加载器会尝试去自己加载。

优势

  • 避免类的重复加载
  • 保护程序安全,防止核心api被随意篡改,如自定义java.lang.String等都不会被调用(沙箱安全机制)

    OpenJdk源码
protected Class loadClass(String name, boolean resolve)
    throws ClassNotFoundException
    {
        // 添加synchronized同步锁防止多个线程调用同一个ClassLoader去加载同一个类产生冲突
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            //如果加载过,则直接return,没有加载过,则进入if判断
            Class c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    //如果当前的类加载器的parent不为空,则递归调用父类的loadClass去加载类。
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    }
                    //如果此时已经是ExtClassLoader,它的parent是null,
                    //将会调用findBootstrapClassOrNull(name)
                    else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                    //从非空父类加载器没有加载到(向上委托加载不到)
                }
                //如果向上委托调用到bootstrapClassLoader还是没有成功,返回了null,进入下面的if判断中
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    //首先现在是ExtClassLoader的findClass,再是AppClassLoader,
                    //最后是调用用户自定义的ClassLoader的findClass方法,
                    //就是层层向外去检查。如果最终还是没有找到,
                    //就会抛出ClassNotFoundException
                    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) {
                //解析这个class
                resolveClass(c);
            }
            return c;
        }
    }
打破双亲委派
  • 打破双亲委派的意思其实就是不委派、向下委派
  • 因为在某些情况下父类加载器需要委托子类加载器去加载class文件。
  • 受到加载范围的限制,父类加载器无法加载到需要的文件,以Driver接口为例,由于Driver接口定义在jdk当中的,而其实现由各个数据库的服务商来提供,比如mysql的就写了MySQL Connector,
  • 那么问题就来了,DriverManager(也由jdk提供)要加载各个实现了Driver接口的实现类,然后进行管理,但是DriverManager由启动类加载器加载,只能记载JAVA_HOME的lib下文件,而其实现是由服务商提供的,由系统类加载器加载,这个时候就需要启动类加载器来委托子类来加载Driver实现,从而破坏了双亲委派。
  • 类似这样的情况就需要打破双亲委派。
  • tomcat打破源码 : org.apache.catalina.loader.WebappClassLoaderBase#loadClass(java.lang.String, boolean)
自定义类加载器
  • 继承ClassLoader,重写findClass/loadClass
  • 参考博客 https://blog.csdn.net/huazai30000/article/details/85296671
  • 遵循双亲委派,如果使用自定义加载器去加载其他加载器可加载的文件,则不会生效
@Override
public Class findClass(String path) {
    byte[] b = loadClassData(path);
    if (Objects.isNull(b)) {
        return null;
    }
    return defineClass(path, b, 0, b.length);
}

private byte[] loadClassData(String path) {
    // load the class data from the connection
    File file = new File(path);
    if (file.exists()) {
        FileInputStream in = null;
        ByteArrayOutputStream out = null;
        try {
            in = new FileInputStream(file);
            out = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int size = 0;
            while ((size = in.read(buffer)) != -1) {
                out.write(buffer, 0, size);
            }
            return out.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {

                e.printStackTrace();
            }
        }
    }
    return null;
}
SPI机制
  • 一种服务发现机制。
  • 它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。
  • 这一机制为很多框架扩展提供了可能,比如在SpringBoot自动配置能力, Dubbo, JDBC中都使用到SPI机制
线程上下文类加载器
  • 一种特殊的类加载器,可以通过Thread获取,基于此可实现逆向委托加载
  • 为了解决双亲委派的缺陷而生
  • 获取 Thread.currentThread().getContextClassLoader()
  • 设置Thread.currentThread().setContextClassLoader(new xxxClassLoadere());
沙箱安全
  • 自定义定义了一个类名为String所在包为java.lang,
  • 这个类本来是属于jdk的,如果没有沙箱安全机制的话,这个类将会污染到我所有的String,
  • 由于沙箱安全机制,所以就委托顶层的bootstrap加载器查找这个类,
  • 如果没有的话就委ext,ext没有就到Aapclassloader,
  • 由于String就是jdk的源代码,所以在bootstrap那里就加载到了,先找到先使用,
  • 就使用bootstrap里面的String,后面的一概不能使用,这就保证了不被恶意代码污染

OpenJdk源码

  • 沙箱安全检查机制方法: AccessController.doPrivileged
private static synchronized void initSystemClassLoader() {
    if (!sclSet) {
        if (scl != null)
            throw new IllegalStateException("recursive invocation");
        sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
        if (l != null) {
            Throwable oops = null;
            scl = l.getClassLoader();
            try {
                //沙箱安全检查AccessController.doPrivileged
                scl = AccessController.doPrivileged(
                    new SystemClassLoaderAction(scl));
            } catch (PrivilegedActionException pae) {
                oops = pae.getCause();
                if (oops instanceof InvocationTargetException) {
                    oops = oops.getCause();
                }
            }
            if (oops != null) {
                if (oops instanceof Error) {
                    throw (Error) oops;
                } else {
                    // wrap the exception
                    throw new Error(oops);
                }
            }
        }
        sclSet = true;
    }
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1001357.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号