前言

学习类加载必然离开不了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

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

第二步:Launcher实例的初始化

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

    private static Launcher launcher = new Launcher();

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

public class Launcher {
public Launcher() {
Launcher.ExtClassLoader var1;
try {
/**
* 调用静态内部类的静态方法:获取ExtClassLoader(第一次调用时会初始化)
* 这里的getExtClassLoader()方法使用了DCL模式创建一个ExtClassLoader的单例。
* 初始化的时候,内部包含ExtClassLoader的加载路径:java.ext.dirs
*/
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
/**
* 调用静态内部类的静态方法:获取AppClassLoader(第一次调用时会初始化)
* 这里的getAppClassLoader()方法没有什么特殊的,就是包含AppClassLoader的加载路径:java.class.path
*/
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
//设置当前线程上下文类加载器为AppClassLoader
Thread.currentThread().setContextClassLoader(this.loader);
String var2 = System.getProperty("java.security.manager");
if (var2 != null) {
SecurityManager var3 = null;
if (!"".equals(var2) && !"default".equals(var2)) {
try {
//SecurityManager默认是被AppClassLoader加载的
var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
} catch (IllegalAccessException var5) {
} catch (InstantiationException var6) {
} catch (ClassNotFoundException var7) {
} catch (ClassCastException var8) {
}
} else {
var3 = new SecurityManager();
}
if (var3 == null) {
throw new InternalError("Could not create SecurityManager: " + var2);
}
System.setSecurityManager(var3);
}
}
}

第三步:ClassLoader.initSystemClassLoader()

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

    private static synchronized void initSystemClassLoader() {
//如果systemClassLoader没有被设置
if (!sclSet) {
if (scl != null)
throw new IllegalStateException("recursive invocation");
//获取Launcher
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
if (l != null) {
Throwable oops = null;
//获取Launcher的AppClassLoader
scl = l.getClassLoader();
try {
//使用SystemClassLoaderAction类暴露修改SystemClassLoader的功能给User
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;
}
}

第四步:利用SystemClassLoaderAction修改AppClassLoader

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

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

第五步:三个参数的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文档:
     * @param name       fully qualified name of the desired class
* @param initialize if {@code true} the class will be initialized.
* See Section 12.4 of <em>The Java Language Specification</em>.
* @param loader class loader from which the class must be loaded
* @return class object representing the desired class

sun.misc.Launcher中的内部类

AppClassLoader

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

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

ExtClassLoader

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

        private static File[] getExtDirs() {
String var0 = System.getProperty("java.ext.dirs");
File[] var1;
if (var0 != null) {
StringTokenizer var2 = new StringTokenizer(var0, File.pathSeparator);
int var3 = var2.countTokens();
var1 = new File[var3]; for(int var4 = 0; var4 < var3; ++var4) {
var1[var4] = new File(var2.nextToken());
}
} else {
var1 = new File[0];
} return var1;
}

【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. vue ele table表格 设置只能勾选一个

    table 更改属性设置: <el-table ref="multipleTable" :data="tableData" tooltip-effect= ...

  2. sql常用查询命令

    目录 SQL Server常用查询命令: 查看当前时间 查询所有数据库名 查询当前使用的数据库名 查询前几条数据 去重查询 字段换名 查询不等于 查询在两个值之间数据 查询条件或 模糊匹配查询 查询为 ...

  3. python轻量级orm框架 peewee常用功能速查

    peewee常用功能速查 peewee 简介 Peewee是一种简单而小的ORM.它有很少的(但富有表现力的)概念,使它易于学习和直观的使用. 常见orm数据库框架 Django ORM peewee ...

  4. Object.keys( )与 for in 区别

    for in 一般用于对象的遍历: let obj = { a:1, b:2, } for(let key in obj){ console.log(key) } // a // b Object.k ...

  5. Devexpress gridcontrol设置列样式

    <dxg:GridControl.Columns><dxg:GridColumn Header="排名" FieldName="UserRank&quo ...

  6. 使用 elementUI 的表单进行查询,表单中只有一个文本框时,回车会自动触发表单的提交事件,导致页面的刷新。

    使用elementUI的el-form组件进行查询时,当输入框仅有一项时,回车自动提交表单,浏览器会刷新页面: 原因:由于当表单只有一个文本框时,按下回车将会触发表单的提交事件, 从而导致页面刷新. ...

  7. dede5.7 给栏目添加上缩略图

    如我们一个栏目列表都用缩略图来表示,而不仅仅只是文字,如果没有这项功能会非常麻烦,网上有很多这方面的资料,但是都试过了有很多问题,自己研究一下,测试基本通过.新加字段 typeimg后台执行SQL: ...

  8. windows 10玩mysql 8

    注意事项: 1)windows 10只支持两个版本: 5.7,8.0 2)安装有两种方式,zip与installer,建议用zip方式,因为installer要安装许多依赖,如vision c++等, ...

  9. 鸿蒙内核源码分析(源码注释篇) | 鸿蒙必定成功,也必然成功 | 百篇博客分析OpenHarmony源码 | v13.02

    百篇博客系列篇.本篇为: v13.xx 鸿蒙内核源码分析(源码注释篇) | 鸿蒙必定成功,也必然成功 | 51.c.h .o 几点说明 kernel_liteos_a_note | 中文注解鸿蒙内核 ...

  10. P4590-[TJOI2018]游园会【dp套dp】

    正题 题目链接:https://www.luogu.com.cn/problem/P4590 题目大意 给出一个长度为\(m\)的字符串\(s\). 对于每个\(k\in[0,m]\)求有多少个长度为 ...