前言

学习类加载必然离开不了sun.misc.Launcher这个类和Class.forName()这个方法。

分析ClassLoader.getSystemClassLoader()这个流程可以明白下面几个知识点:

  • sun.misc.Launcher的初始化

  • 初次接触线程上下文类加载器(Thread context class loader)

  • 三个参数的Class.forName(String name, boolean initialize, ClassLoader loader)方法

  • 怎样修改JVM默认的系统类加载器

  • Launcher类中存在两个很重要的内部类:AppClassLoader和ExtClassLoader

  • Launcher类主要负责AppClassLoader的初始化/ExtClassLoader的初始化/线程上下文类加载器的初始化

  • Class.forName()是JDK提供给我们用于加载一个Class文件的方法

第一步:ClassLoader.getSystemClassLoader()

当客户端希望获取系统类加载器的时候,需要第一次调用ClassLoader.getSystemClassLoader()静态方法,该方法第一步即会去尝试获取一个sun.misc.Launcher

  1. sun.misc.Launcher l = sun.misc.Launcher.getLauncher();

第二步:Launcher实例的初始化

这里的实例初始化是不区分方法的,这是Launcher类的静态变量的初始化,通过之前学习类加载的知识可以知道:类初始化后,类的静态变量会初始化。所以下面的代码得到调用:

  1. private static Launcher launcher = new Launcher();

下面我们看一下Launcher的无参构造器:

  1. public class Launcher {
  2. public Launcher() {
  3. Launcher.ExtClassLoader var1;
  4. try {
  5. /**
  6. * 调用静态内部类的静态方法:获取ExtClassLoader(第一次调用时会初始化)
  7. * 这里的getExtClassLoader()方法使用了DCL模式创建一个ExtClassLoader的单例。
  8. * 初始化的时候,内部包含ExtClassLoader的加载路径:java.ext.dirs
  9. */
  10. var1 = Launcher.ExtClassLoader.getExtClassLoader();
  11. } catch (IOException var10) {
  12. throw new InternalError("Could not create extension class loader", var10);
  13. }
  14. try {
  15. /**
  16. * 调用静态内部类的静态方法:获取AppClassLoader(第一次调用时会初始化)
  17. * 这里的getAppClassLoader()方法没有什么特殊的,就是包含AppClassLoader的加载路径:java.class.path
  18. */
  19. this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
  20. } catch (IOException var9) {
  21. throw new InternalError("Could not create application class loader", var9);
  22. }
  23. //设置当前线程上下文类加载器为AppClassLoader
  24. Thread.currentThread().setContextClassLoader(this.loader);
  25. String var2 = System.getProperty("java.security.manager");
  26. if (var2 != null) {
  27. SecurityManager var3 = null;
  28. if (!"".equals(var2) && !"default".equals(var2)) {
  29. try {
  30. //SecurityManager默认是被AppClassLoader加载的
  31. var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
  32. } catch (IllegalAccessException var5) {
  33. } catch (InstantiationException var6) {
  34. } catch (ClassNotFoundException var7) {
  35. } catch (ClassCastException var8) {
  36. }
  37. } else {
  38. var3 = new SecurityManager();
  39. }
  40. if (var3 == null) {
  41. throw new InternalError("Could not create SecurityManager: " + var2);
  42. }
  43. System.setSecurityManager(var3);
  44. }
  45. }
  46. }

第三步:ClassLoader.initSystemClassLoader()

说回ClassLoader.getSystemClassLoader()方法,初始化完成Launcher实例之后,下面就是初始化ClassLoader类中的SystemClassLoader了。

  1. private static synchronized void initSystemClassLoader() {
  2. //如果systemClassLoader没有被设置
  3. if (!sclSet) {
  4. if (scl != null)
  5. throw new IllegalStateException("recursive invocation");
  6. //获取Launcher
  7. sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
  8. if (l != null) {
  9. Throwable oops = null;
  10. //获取Launcher的AppClassLoader
  11. scl = l.getClassLoader();
  12. try {
  13. //使用SystemClassLoaderAction类暴露修改SystemClassLoader的功能给User
  14. scl = AccessController.doPrivileged(
  15. new SystemClassLoaderAction(scl));
  16. } catch (PrivilegedActionException pae) {
  17. oops = pae.getCause();
  18. if (oops instanceof InvocationTargetException) {
  19. oops = oops.getCause();
  20. }
  21. }
  22. if (oops != null) {
  23. if (oops instanceof Error) {
  24. throw (Error) oops;
  25. } else {
  26. // wrap the exception
  27. throw new Error(oops);
  28. }
  29. }
  30. }
  31. sclSet = true;
  32. }
  33. }

第四步:利用SystemClassLoaderAction修改AppClassLoader

SystemClassLoaderAction类不是内部类,它是ClassLoader平级的类,但不是public的。

  1. class SystemClassLoaderAction
  2. implements PrivilegedExceptionAction<ClassLoader> {
  3. private ClassLoader parent;
  4. SystemClassLoaderAction(ClassLoader parent) {
  5. this.parent = parent;
  6. }
  7. public ClassLoader run() throws Exception {
  8. //获取系统属性java.system.class.loader
  9. String cls = System.getProperty("java.system.class.loader");
  10. if (cls == null) {
  11. //系统属性java.system.class.loader为空,返回默认的AppClassLoader【这种情况为默认情况】
  12. return parent;
  13. }
  14. /**
  15. * 若系统属性java.system.class.loader(设为X)不为空,反射获取x的class(X为二进制名字)
  16. * 然后把X对应的类加载器反射初始化,设置为系统类加载器。
  17. * 并将X设置为线程上下文类加载器。
  18. * 注意这里的Class.forName(String name, boolean initialize, ClassLoader loader)方法
  19. */
  20. Constructor<?> ctor = Class.forName(cls, true, parent)
  21. .getDeclaredConstructor(new Class<?>[] { ClassLoader.class });
  22. ClassLoader sys = (ClassLoader) ctor.newInstance(
  23. new Object[] { parent });
  24. Thread.currentThread().setContextClassLoader(sys);
  25. return sys;
  26. }
  27. }

第五步:三个参数的Class.forName(String name, boolean initialize, ClassLoader loader)方法

  • 参数1:类的全名
  • 参数2:true表示初始化这个class,false表示不需初始化这个class
  • 参数3:loader表示希望用哪个类加载器来加载该类
  • 返回 :代表这个名字的类的Class对象

    Class.forName(String name, boolean initialize, ClassLoader loader)方法的部分JavaDoc文档:
  1. * @param name fully qualified name of the desired class
  2. * @param initialize if {@code true} the class will be initialized.
  3. * See Section 12.4 of <em>The Java Language Specification</em>.
  4. * @param loader class loader from which the class must be loaded
  5. * @return class object representing the desired class

sun.misc.Launcher中的内部类

AppClassLoader

可以看到Launcher类中存在一个静态内部类AppClassLoader,其中包含如下方法,所以这就是该问题的答案:为何应用类加载器是从java.class.path路径中加载类?

  1. public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
  2. final String var1 = System.getProperty("java.class.path");
  3. final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
  4. return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
  5. public Launcher.AppClassLoader run() {
  6. URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
  7. return new Launcher.AppClassLoader(var1x, var0);
  8. }
  9. });
  10. }

ExtClassLoader

Launcher类中还存在一个静态内部类ExtClassLoader,其中包含如下方法,在首次创建扩展类加载器的时候被调用,所以这就是该问题的答案:为何扩展类加载器是从java.ext.dirs路径中加载类?

  1. private static File[] getExtDirs() {
  2. String var0 = System.getProperty("java.ext.dirs");
  3. File[] var1;
  4. if (var0 != null) {
  5. StringTokenizer var2 = new StringTokenizer(var0, File.pathSeparator);
  6. int var3 = var2.countTokens();
  7. var1 = new File[var3];
  8. for(int var4 = 0; var4 < var3; ++var4) {
  9. var1[var4] = new File(var2.nextToken());
  10. }
  11. } else {
  12. var1 = new File[0];
  13. }
  14. return var1;
  15. }

【Java虚拟机10】ClassLoader.getSystemClassLoader()流程简析的更多相关文章

  1. Java虚拟机运行时内存区域简析

    figure:first-child { margin-top: -20px; } #write ol, #write ul { position: relative; } img { max-wid ...

  2. Java虚拟机JVM学习01 流程概述

    Java虚拟机JVM学习01 流程概述 Java虚拟机与程序的生命周期 一个运行时的Java虚拟机(JVM)负责运行一个Java程序. 当启动一个Java程序时,一个虚拟机实例诞生:当程序关闭退出,这 ...

  3. Tomcat启动流程简析

    Tomcat是一款我们平时开发过程中最常用到的Servlet容器.本系列博客会记录Tomcat的整体架构.主要组件.IO线程模型.请求在Tomcat内部的流转过程以及一些Tomcat调优的相关知识. ...

  4. Java虚拟机10:类加载器

    类与类加载器 虚拟机设计团队把类加载阶段张的"通过一个类的全限定名来获取此类的二进制字节流"这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类.实现这 ...

  5. JAVA JVM常见内存参数配置简析

    JVM常见内存参数配置简析   常见参数 -Xms .-Xmx.-XX:newSize.-XX:MaxnewSize.-Xmn(-XX:newSize.-XX:MaxnewSize) 简析 1.-Xm ...

  6. zxing二维码扫描的流程简析(Android版)

    目前市面上二维码的扫描似乎用开源google的zxing比较多,接下去以2.2版本做一个简析吧,勿喷... 下载下来后定位两个文件夹,core和android,core是一些核心的库,android是 ...

  7. Java虚拟机10:Client模式和Server模式的区别

    部分商用虚拟机中,Java程序最初是通过解释器对.class文件进行解释执行的,当虚拟机发现某个方法或代码块运行地特别频繁的时候,就会把这些代码认定为热点代码Hot Spot Code(这也是我们使用 ...

  8. java虚拟机10.内存模型与线程

    多任务处理在现代计算机操作系统中是一项必备的功能,让计算机同时去做几件事情,不仅是因为计算机的运算能力强大了,更重要的原因是计算机的运算速度与它的存储和通信子系统速度的差距太大,大量的时间都花费在磁盘 ...

  9. LinkedHashMap结构get和put源码流程简析及LRU应用

    原理这篇讲得比较透彻Java集合之LinkedHashMap. 本文属于源码阅读笔记,因put,get调用逻辑及链表维护逻辑复杂(至少网上其它文章的逻辑描述及配图,我都没看明白LinkedHashMa ...

随机推荐

  1. ECMAScript版本知识点汇总

    ECMAScript版本知识点汇总 ES5 btoa.atob 对参数进行base64格式编码.解码 /** * btoa() * base64编码 * @param {string} str * @ ...

  2. Mybatis源码解析4——SqlSession

    上一篇文章中,我们介绍了 SqlSessionFactory 的创建过程,忘记了的,可以回顾一下,或者看下下面这张图也行. 接下来,可乐讲给大家介绍 Mybatis 中另一个重量级嘉宾--SqlSes ...

  3. vue七种实现组建通信的方法

    目录 组件通信 1.props 父组件--->子组件通信 2.$emit 子组件--->父组件传递 $emit与props结合 兄弟组件传值 3.bus(事件总线) 兄弟组件通信 4.$p ...

  4. Docker容器管理——Docker容器常用命令

    1.查看所有的容器 docker ps 2.查看运行的容器 docker ps -a 3.启动.停止.重启docker容器 docker start ... docker stop ... docke ...

  5. AOP快速入门

    一.概念 AOP面向切面编程,是函数式编程的延申,是对OOP的补充: 代理模式:拦截增强作用,增强功能: 1.java继承,纵向共性抽取, 2.横向切面AOP织入增强代码方式 二.原理是通过代理机制, ...

  6. mysql多次连接后会产生最大失败值

    解决办法 可以更改max_connection_errors的值,即提高允许的max_connection_errors的数量 1.进入mysql 1)首先查看该属性设置为多大:命令:show glo ...

  7. minio & gitlab runner

    Docker安装Minio存储服务器详解 # mkdir -p /data/minio # docker pull nexus3:8089/minio/minio # docker run -p 90 ...

  8. 机器学习——EM算法

    1 数学基础 在实际中,最小化的函数有几个极值,所以最优化算法得出的极值不确实是否为全局的极值,对于一些特殊的函数,凸函数与凹函数,任何局部极值也是全局极致,因此如果目标函数是凸的或凹的,那么优化算法 ...

  9. 每日学习——C++习题

    1.题目要求:求圆的面积,数据成员为半径r,定义为私有成员,要求用成员函数实现在键盘上输入圆半径,计算圆面积.输出圆面积三个功能,要求三个成员函数在类内声明,在类外定义 //定义类 class Cir ...

  10. Mysql将其他表中的数据更新到指定表中

    update tb  set tb.字段= (select 字段 from tb1 where tb.字段1 = tb1.字段1); update role set uid = (select ID ...