我们一般写的java文件jvm是识别不了的,因此需要编译,编译后会变成.class文件,而要执行代码,jvm首先会去加载.class文件到内存中,那么他的流程是什么样的呢:

  1.首先肯定创建java虚拟机(就像我们运行windows的程序首先得安装windows系统一个道理)

  2.同时会创建一个引导类加载器实例(这个由c++实现,也就是我们的BootStrap类加载器)

  3.然后c++代码会调用创建java的启动类launcher创建类加载器,去加载不同类型的类

  4.类加载完成会调用对应的main方法

  5.运行后销毁

流程如下图所示:

具体的类加载流程:加载>>验证>>准备>>解析>>初始化>>使用>>卸载

1.加载:加载就是找到对应的字节码文件并加载到内存

2.验证: 验证字节码文件的格式等正确性

3.准备:加载静态变量到内存并赋值

4.解析:将符号引用转换为直接引用(内存地址)

5.初始化: 将类加载到方法区,执行静态代码块,初始化静态变量值

大概了解之后,我们直接撸代码:

public Launcher() {
Launcher.ExtClassLoader var1;
try {
//获取扩展类加载器extclassLoader
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
} try {
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); //获取应用程序类加载器并将扩展类加载器设为父类加载器
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
} 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 {
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);
} } public ExtClassLoader(File[] var1) throws IOException {
super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);//这里可以看到ext的父类加载器设为null
SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
} AppClassLoader(URL[] var1, ClassLoader var2) { //这里可以看到app的父类加载器就是上面传进来的var1->extClassLoader
super(var1, var2, Launcher.factory);
this.ucp.initLookupCache(this);
}

这就可以看到应用类加载器的父类加载器是扩展类加载器,继续看加载流程,看appClassLoader的loadClass方法:

public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
int var3 = var1.lastIndexOf(46);
if (var3 != -1) {
SecurityManager var4 = System.getSecurityManager();
if (var4 != null) {
var4.checkPackageAccess(var1.substring(0, var3));
}
} if (this.ucp.knownToNotExist(var1)) { //查看是否已经被加载过,获取存在直接
Class var5 = this.findLoadedClass(var1);
if (var5 != null) {
if (var2) {
this.resolveClass(var5);
} return var5;
} else {
throw new ClassNotFoundException(var1);
}
} else { //不存在调用父类
return super.loadClass(var1, var2);
}
}

再看父类,extClassLoader调用的也是这个:

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,已经被加载并返回,否则返回null
if (c == null) { //不存在
long t0 = System.nanoTime();
try {
if (parent != null) { //有父调用父类加载器
c = parent.loadClass(name, false);
} else { //当父类加载器不存在,如extClassLoad设置父类为null(其实是有的,但其父类bootStrapClassLoad在JVM中由c++实现,所以设置为null)
c = findBootstrapClassOrNull(name); //这里点进去看可以发现其实就是获取BootStrapClass
}
} 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;
}
}

这里我们可以理一下顺序:

1.首先会去调用appClassLoad的loadClass方法,如果已经加载过,则直接获取,否则调用父加载方法

2.父类方法依旧会找一次当然还是没有,会去调用父类加载器的loadClass方法(前面我们已经知道appClassLoad父加载器为extClassLoad)

3.递归调用classLoad方法,直到父加载器为空(也就是extClassLoad),会去获取BootStrapClass类加载器调用类加载方法

4.还是获取不到则按顺序查找类,这个我们下面详细再看

protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
final Class<?> result;
try {
result = AccessController.doPrivileged(
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
String path = name.replace('.', '/').concat(".class"); //拼接class文件地址
Resource res = ucp.getResource(path, false); //获取资源
if (res != null) {
try {
return defineClass(name, res); //解析指定的资源类
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
return null;
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
if (result == null) {
throw new ClassNotFoundException(name);
}
return result;
}

其实就是根据类名拼接class文件地址,并解析指定的资源类,再往下基本就是分析c++代码了,这个后续有时间在研究吧.

看完代码,我们再来看一些我们之前背了又忘的理论:双亲委派机制,那么我们再来分析分析:

双亲委派机制:

  

如上图所示,其实就是双亲委派机制,仔细看的话发现和我上面分析代码的逻辑是一样的,是的,上面loadClass递归调用其实就是我们双亲委派机制的实现,而实际的类加载,就在我们的findClass方法里。

这个时候问题就来了,类加载一个类加载器就够了,为什么要搞这么复杂的结构呢?

  答案其实就是为了安全,可靠。我们java中有很多基础类库如Object类,算是我们开发的基石,但是如果说只用一个编辑器的话,当出现外部也定义了一个相同路径名的类时,就可能覆盖或者冲突,无论哪种情况,都会对我们的程序造成不可控的影响。而像上述图中,每个类加载器各司其职,只专注于做自己的事情,这样既不会改变基础类库,也不会出现重复加载的情况。仔细想想,这也是解耦的思想的体现。

双亲委派模型的特色就是,安全,可靠,不会出现重复加载,但是像tomcat这样需要加载多个应用程序的又怎么办呢----->打破双亲委派。

打破双亲委派

双亲委派机制是很厉害的,但jdk也给我们提供了自定义类加载的拓展,其实就是让我们有更多的实现,其实要打破的实现也很简单,就是重写loadClass方法,将所需要打破的直接findClass,而不去调用父类的findClass,代码如下:

 protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class<?> c = findLoadedClass(name);
if (c == null) {
long t = System.nanoTime();
if(!name.startsWith("broker")){ //可以将需要打破双亲委派的单独放一个包里
c = super.loadClass(name);
}else {
c = findClass(name);
}
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t);
sun.misc.PerfCounter.getFindClasses().increment();
}
if (resolve) {
resolveClass(c);
}
return c;
}
}

tomcat打破双亲委派:

  tomcat是服务部署容器,首先我们得了解他有哪些问题:

  1.在部署多应用程序时,可能出现多个程序不同版本,因此,每个程序依赖需要相互独立,互相隔离。

  2.相同版本类库不需要重新加载。

  3.web容器和应用容器隔离

  4.jsp文件热加载

tomcat为了解决这个问题,肯定是需要打破双亲委派模型的,毕竟应用之间有需要隔离的,web容器和应用容器也需要隔离,而jsp文件预加载则需要改动后卸载掉重新加载,总结一下就是:

第一个问题,如果使用默认的类加载器机制,那么是无法加载两个相同类库的不同版本的,默认的类加器是不管你是什么版本的,只在乎你的全限定类名,并且只有一份。

第二个问题,默认的类加载器是能够实现的,因为他的职责就是保证唯一性。

第三个问题和第一个问题一样。

我们再看第四个问题,我们想我们要怎么实现jsp文件的热加载,jsp文件其实也就是class文件,那么如果修改了,但类名还是一样,类加载器会直接取方法区中已经存在的,修改后的jsp是不会重新加载的。那么怎么办呢?我们可以直接卸载掉这jsp文件的类加载器,所以你应该想到了,每个jsp文件对应一个唯一的类加载器,当一个jsp文件修改了,就直接卸载这个jsp类加载器。重新创建类加载器,重新加载jsp文件。

具体如下图所示:

tomcat类加载器分的很多,各司其职,其实我在想,可能一开始tomcat也没有那么多,也许就多一个tomcat类加载器,后来多服务部署发现不同版本兼容性等问题,于是又新增类加载器去隔离,后来jsp出现又新增,慢慢发展到这么复杂的地步吧。

总结:

  这一章呢,从分析源码入手,讲解了类加载的过程,以及双亲委派模型的实现,从源码角度去看,双亲委派机制还是很好理解的。最后介绍了tomcat如何打破双亲委派机制。类的加载流程很简单,双亲委派机制去加载不同类,但实现很复杂,需要考虑的问题太多,验证,解析等等都是非常复杂的流程。大体上算是了解了,但想理解,可能还是得去撸jvm源码了。

.tb_button { padding: 1px; cursor: pointer; border-right: 1px solid rgba(139, 139, 139, 1); border-left: 1px solid rgba(255, 255, 255, 1); border-bottom: 1px solid rgba(255, 255, 255, 1) }
.tb_button.hover { borer: 2px outset #def; background-color: rgba(248, 248, 248, 1) !important }
.ws_toolbar { z-index: 100000 }
.ws_toolbar .ws_tb_btn { cursor: pointer; border: 1px solid rgba(85, 85, 85, 1); padding: 3px }
.tb_highlight { background-color: rgba(255, 255, 0, 1) }
.tb_hide { visibility: hidden }
.ws_toolbar img { padding: 2px; margin: 0 }
.tb_button { padding: 1px; cursor: pointer; border-right: 1px solid rgba(139, 139, 139, 1); border-left: 1px solid rgba(255, 255, 255, 1); border-bottom: 1px solid rgba(255, 255, 255, 1) }
.tb_button.hover { borer: 2px outset #def; background-color: rgba(248, 248, 248, 1) !important }
.ws_toolbar { z-index: 100000 }
.ws_toolbar .ws_tb_btn { cursor: pointer; border: 1px solid rgba(85, 85, 85, 1); padding: 3px }
.tb_highlight { background-color: rgba(255, 255, 0, 1) }
.tb_hide { visibility: hidden }
.ws_toolbar img { padding: 2px; margin: 0 }

手撸jdk源码分析类加载机制的更多相关文章

  1. MyBatis 源码分析 - 插件机制

    1.简介 一般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者自行拓展.这样的好处是显而易见的,一是增加了框架的灵活性.二是开发者可以结合实际需求,对框架进行拓展,使其能够更好的工作.以 My ...

  2. JVM源码分析-类加载场景实例分析

    A类调用B类的静态方法,除了加载B类,但是B类的一个未被调用的方法间接使用到的C类却也被加载了,这个有意思的场景来自一个提问:方法中使用的类型为何在未调用时尝试加载?. 场景如下: public cl ...

  3. JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue

    JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue 目的:本文通过分析JDK源码来对比ArrayBlockingQueue 和LinkedBlocki ...

  4. springMVC源码分析--异常处理机制HandlerExceptionResolver执行原理(二)

    上一篇博客springMVC源码分析--异常处理机制HandlerExceptionResolver简单示例(一)中我们简单地实现了一个异常处理实例,接下来我们要介绍一下HandlerExceptio ...

  5. JDK 源码分析(4)—— HashMap/LinkedHashMap/Hashtable

    JDK 源码分析(4)-- HashMap/LinkedHashMap/Hashtable HashMap HashMap采用的是哈希算法+链表冲突解决,table的大小永远为2次幂,因为在初始化的时 ...

  6. JDK源码分析(三)—— LinkedList

    参考文档 JDK源码分析(4)之 LinkedList 相关

  7. JDK源码分析(一)—— String

    dir 参考文档 JDK源码分析(1)之 String 相关

  8. JDK源码分析(2)LinkedList

    JDK版本 LinkedList简介 LinkedList 是一个继承于AbstractSequentialList的双向链表.它也可以被当作堆栈.队列或双端队列进行操作. LinkedList 实现 ...

  9. 【JDK】JDK源码分析-LinkedHashMap

    概述 前文「JDK源码分析-HashMap(1)」分析了 HashMap 主要方法的实现原理(其他问题以后分析),本文分析下 LinkedHashMap. 先看一下 LinkedHashMap 的类继 ...

  10. 【JDK】JDK源码分析-HashMap(1)

    概述 HashMap 是 Java 开发中最常用的容器类之一,也是面试的常客.它其实就是前文「数据结构与算法笔记(二)」中「散列表」的实现,处理散列冲突用的是“链表法”,并且在 JDK 1.8 做了优 ...

随机推荐

  1. Swift高级进阶-Swift编译过程,”SIL代码“,“IR语法”

    swift编译过程 如果不懂LLVM,Clang的同学可以去了解下它的知识点  一些文章中有详细介绍 OC 的编译过程 ,本文来探索一下 Swift 的编译过程.Swift 的编译过程中使用 Swif ...

  2. DataGear 制作联动异步加载图表的数据可视化看板

    通过DataGear的参数化数据集.图表事件处理和看板API功能,可以很方便地制作联动异步加载图表的数据可视化看板. 首先,新建一个参数化SQL数据集,如下所示: SELECT COL_NAME, - ...

  3. 【Azure Service Fabric】关于Service Fabric的相关问题

    问题一:Service Fabric 是否支持Private Link? 在Azure Private Endpoint文档中,罗列出了 Azure 上支持 Private Link 的服务.Serv ...

  4. 【Azure Redis 缓存】Lettuce 连接到Azure Redis服务,出现15分钟Timeout问题

    问题描述 在Java应用中,使用 Lettuce 作为客户端SDK与Azure Redis 服务连接,当遇见连接断开后,长达15分钟才会重连.导致应用在长达15分的时间,持续报错Timeout 问题解 ...

  5. 【Azure 应用服务】在App Service 中如何通过Managed Identity获取访问Azure资源的Token呢? 如Key Vault

    问题描述 当App Service启用了Managed Identity后,Azure中的资源就可以使用此Identity访问. 如果需要显示的获取这个Token,如何实现呢? 问题解答 在App S ...

  6. 【Azure 应用服务】Azure Function (PowerShell) 执行时报错 "out of memory"

    问题描述 在Azure Function App服务中,创建一个PowerShell脚本的函数,遇见了OOM(Out Of Memory)的异常报错: 2022-01-10T07:44:37 Welc ...

  7. [杂项] 抢单可能需要的API

    淘宝时间戳 http://api.m.taobao.com/rest/api3.do?api=mtop.common.getTimestamp 京东库存查询 http://c0.3.cn/stock? ...

  8. Server-side template injection 模板注入问题总结

    概念: 服务器模板注入(Server-side template injection) 攻击者能够使用本地的模板语法去注入一个恶意的payload,然后在服务器端执行该攻击,当与欧股直接输入数据到模板 ...

  9. .Net下的CORS跨域设置

    CORS跨域访问问题往往出现在"浏览器客户端"通过ajax调用"服务端API"的时候.而且若是深究原理,还会发现跨域问题其实还分为[简单跨域]与[复杂跨域]这两 ...

  10. vscode 格式化 vue 等文件的 配置 eslint vetur prettier Beautify

    需求 自动格式化需求 多行回车 合并一行,去分号 最后一个逗号,自动删除,符合eslint 结果 虽然能用了,但是 百度好几个方案,也不知道哪个对哪个,太忙没时间弄了. 配置文件记录 eslint 得 ...