手撸jdk源码分析类加载机制
我们一般写的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源码分析类加载机制的更多相关文章
- MyBatis 源码分析 - 插件机制
		1.简介 一般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者自行拓展.这样的好处是显而易见的,一是增加了框架的灵活性.二是开发者可以结合实际需求,对框架进行拓展,使其能够更好的工作.以 My ... 
- JVM源码分析-类加载场景实例分析
		A类调用B类的静态方法,除了加载B类,但是B类的一个未被调用的方法间接使用到的C类却也被加载了,这个有意思的场景来自一个提问:方法中使用的类型为何在未调用时尝试加载?. 场景如下: public cl ... 
- JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue
		JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue 目的:本文通过分析JDK源码来对比ArrayBlockingQueue 和LinkedBlocki ... 
- springMVC源码分析--异常处理机制HandlerExceptionResolver执行原理(二)
		上一篇博客springMVC源码分析--异常处理机制HandlerExceptionResolver简单示例(一)中我们简单地实现了一个异常处理实例,接下来我们要介绍一下HandlerExceptio ... 
- JDK 源码分析(4)—— HashMap/LinkedHashMap/Hashtable
		JDK 源码分析(4)-- HashMap/LinkedHashMap/Hashtable HashMap HashMap采用的是哈希算法+链表冲突解决,table的大小永远为2次幂,因为在初始化的时 ... 
- JDK源码分析(三)—— LinkedList
		参考文档 JDK源码分析(4)之 LinkedList 相关 
- JDK源码分析(一)—— String
		dir 参考文档 JDK源码分析(1)之 String 相关 
- JDK源码分析(2)LinkedList
		JDK版本 LinkedList简介 LinkedList 是一个继承于AbstractSequentialList的双向链表.它也可以被当作堆栈.队列或双端队列进行操作. LinkedList 实现 ... 
- 【JDK】JDK源码分析-LinkedHashMap
		概述 前文「JDK源码分析-HashMap(1)」分析了 HashMap 主要方法的实现原理(其他问题以后分析),本文分析下 LinkedHashMap. 先看一下 LinkedHashMap 的类继 ... 
- 【JDK】JDK源码分析-HashMap(1)
		概述 HashMap 是 Java 开发中最常用的容器类之一,也是面试的常客.它其实就是前文「数据结构与算法笔记(二)」中「散列表」的实现,处理散列冲突用的是“链表法”,并且在 JDK 1.8 做了优 ... 
随机推荐
- 【LeetCode回溯算法#03】电话号码的字母组合(数字映射字母)
			电话号码的字母组合 力扣题目链接(opens new window) 给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合. 给出数字到字母的映射如下(与电话按键相同).注意 1 不对应任 ... 
- 第一百一十篇:内存泄漏和垃圾回收(JS)
			好家伙,本篇内容为<JS高级程序设计>第四章的学习笔记 1.内存泄露 1.1.什么是内存泄漏? 内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释 ... 
- DataGear 制作自定义柱状图条目颜色的数据可视化看板
			DataGear 看板提供了dg-chart-options图表选项配置功能,可自定义样式.位置.显示内容等图表选项,其中的processUpdateOptions回调函数配置项,可以在图表更新数据前 ... 
- webpack图片压缩
			减少代码体积 | 尚硅谷 Web 前端之 Webpack5 教程 (yk2012.github.io) npm install image-mininizer webpack plugin image ... 
- 【Azure Data Lake Storage】如何才能保留Blob中的文件目录结构(即使文件夹中文件数量为0的情况下)?
			问题描述 在使用Azure Storage Account Blob时候,发现当文件夹中的Blob(文件)被全部输出后,文件夹也会消失? 为什么它不能像Windows系统中的文件夹一样,即使是一个空文 ... 
- TensorFlow 回归模型
			TensorFlow 回归模型 首先,导入所需的库和模块.代码中使用了numpy进行数值计算,matplotlib进行数据可视化,tensorflow进行机器学习模型的构建和训练,sklearn进行多 ... 
- 1. Canal入门
			1. Canal简介 官方文档: https://github.com/alibaba/canal/wiki/简介 早期,阿里巴巴B2B公司因为存在杭州和美国双机房部署,存在跨机房同步的业务需求.早期 ... 
- 国内RPA融资年终大盘点:19起投资总额破34亿估值近230亿,垂直落地之年开启
			2021国内RPA融资年终盘点:15家厂商融资总额破34亿,估值近230亿 2021RPA融资年终大盘点:19起投资估值近230亿,垂直落地之年开启 2021国内RPA融资年终盘点:15家厂商19起投 ... 
- 一文搞定POI,再也不怕excel导入导出了
			写在前面 在Java日常开发过程中,实现Excel文件的导入导出功能是一项常见的需求. 通过使用相关的Java库,如Apache POI.EasyPoi或EasyExcel,可以轻松地实现Excel文 ... 
- python 文件操作常用方法
			一 python文件创建 import os import argparse def file_write(file_name,msg): f = open(file_name,"a&quo ... 
