【Java虚拟机10】ClassLoader.getSystemClassLoader()流程简析
前言
学习类加载必然离开不了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()流程简析的更多相关文章
- Java虚拟机运行时内存区域简析
figure:first-child { margin-top: -20px; } #write ol, #write ul { position: relative; } img { max-wid ...
- Java虚拟机JVM学习01 流程概述
Java虚拟机JVM学习01 流程概述 Java虚拟机与程序的生命周期 一个运行时的Java虚拟机(JVM)负责运行一个Java程序. 当启动一个Java程序时,一个虚拟机实例诞生:当程序关闭退出,这 ...
- Tomcat启动流程简析
Tomcat是一款我们平时开发过程中最常用到的Servlet容器.本系列博客会记录Tomcat的整体架构.主要组件.IO线程模型.请求在Tomcat内部的流转过程以及一些Tomcat调优的相关知识. ...
- Java虚拟机10:类加载器
类与类加载器 虚拟机设计团队把类加载阶段张的"通过一个类的全限定名来获取此类的二进制字节流"这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类.实现这 ...
- JAVA JVM常见内存参数配置简析
JVM常见内存参数配置简析 常见参数 -Xms .-Xmx.-XX:newSize.-XX:MaxnewSize.-Xmn(-XX:newSize.-XX:MaxnewSize) 简析 1.-Xm ...
- zxing二维码扫描的流程简析(Android版)
目前市面上二维码的扫描似乎用开源google的zxing比较多,接下去以2.2版本做一个简析吧,勿喷... 下载下来后定位两个文件夹,core和android,core是一些核心的库,android是 ...
- Java虚拟机10:Client模式和Server模式的区别
部分商用虚拟机中,Java程序最初是通过解释器对.class文件进行解释执行的,当虚拟机发现某个方法或代码块运行地特别频繁的时候,就会把这些代码认定为热点代码Hot Spot Code(这也是我们使用 ...
- java虚拟机10.内存模型与线程
多任务处理在现代计算机操作系统中是一项必备的功能,让计算机同时去做几件事情,不仅是因为计算机的运算能力强大了,更重要的原因是计算机的运算速度与它的存储和通信子系统速度的差距太大,大量的时间都花费在磁盘 ...
- LinkedHashMap结构get和put源码流程简析及LRU应用
原理这篇讲得比较透彻Java集合之LinkedHashMap. 本文属于源码阅读笔记,因put,get调用逻辑及链表维护逻辑复杂(至少网上其它文章的逻辑描述及配图,我都没看明白LinkedHashMa ...
随机推荐
- 【Elasticsearch】.NetCore中Elasticsearch组件NEST的使用
.NetCore中Elasticsearch组件NEST的使用 1. 安装Docker # 安装Docker curl -fsSL https://get.docker.com | bash -s d ...
- Java程序中使用Spire Jar包报java.lang.NoSuchMethodError类型错误的解决方法
Jar包功能概述 使用Spire系列的Jar包可以操作Word.Excel.PPT.PDF.Barcode等格式的文件,分别对应使用的jar包是Spire.Doc for Java.Spire.XLS ...
- RabbitMQ详解(一)——
RabbitMQ详解(一)-- https://www.cnblogs.com/liuwenwu9527/p/11989216.html https://www.cnblogs.com/ideal-2 ...
- com.google.zxing.NotFoundException-识别图片二维码信息错误
一.问题由来 自己在做一个小程序项目的后台,其中需要使用到识别图片二维码信息,而且是必须在Java后台进行识别二维码操作.去百度里面很快找到一个方法, 可以识别简单的二维码,而且自己生成的简单的二维码 ...
- Junit5快速入门指南-3
Fixtures 是测试中非常重要的一部分.他们的主要目的是建立一个固定/已知的环境状态以确保 测试可重复并且按照预期的方式运行.比如在app测试中 基类@BeforeClass 配置初始化,初始化d ...
- python3 爬虫五大模块之五:信息采集器
Python的爬虫框架主要可以分为以下五个部分: 爬虫调度器:用于各个模块之间的通信,可以理解为爬虫的入口与核心(main函数),爬虫的执行策略在此模块进行定义: URL管理器:负责URL的管理,包括 ...
- vue项目 'node-sass'问题
Cannot find module 'node-sass' 解决办法: 运行命令:cnpm install node-sass@latest 即可解决,( 网络差的同学可以选择重新下载no-modu ...
- SparkPi的编程计算
Pi的计算方式有很多,本文主要是通过Spark在概论统计的方法对Pi进行求解: 算法说明: 在边长为R的正方形中,其面积为R^2,而其内接圆的面积为pi * R^2 /4 ,圆的面积与正方形的面积比为 ...
- 浅谈VMware的NAT模式
什么是NAT模式?理论化的措辞我就不说了,我将结合本人平时的经验来简单的说明一下NAT模式,以及配置NAT模式时遇到的问题. 大家都知道,我们的电脑要想联网,需要与交换机连接,假设交换机的网关为192 ...
- Spring Cloud Eureka 之常用配置解析
[原创内容,转载.引用请注明出处] 1. 配置项解析 1.1 通用配置 # 应用名称,将会显示在Eureka界面的应用名称列 spring.application.name=config-servic ...