一.类加载器
1.认识类加载器

类加载器以二进制流的方式从不同渠道·获取字节码文件并将获取到的数据交给虚拟机。
然后由虚拟机调用本地接口创建方法区对象和堆区对象储存字节码信息到内存中。
2.类加载器的分类
类加载器分为两类,一类由虚拟机底层实现,一类由java代码实现(jdc提供了不同用途的类加载器,程序员也可以根据需要自己实现)。
JDK8及之前类加载器主要由以下几种
由虚拟机底层实现的启动类加载器BookStrap 加载核心类
由java实现的扩展类加载器extension 允许扩展java代码中通用的类 以及应用程序加载类application 加载程序使用的类。
3.BookStrap启动类加载器
启动类加载器加载JDK核心类库。
主要加载java安装目录/jre/lib下的类文件
并且我们可以通过两种方式实现启动类加载器加载的核心类库中的类拓展
:
-
放入jre/lib下进行扩展。不推荐,尽可能不要去更改JDK安装目录中的内容,会出现即时放进去由于文件名不匹配的问题也不会正常地被加载。
-
**使用参数进行扩展。**推荐,使用-Xbootclasspath/a:jar包目录/jar包名 进行扩展,参数中的/a代表新增。
4.extension扩展类加载器
主要扩展java程序中通用的类
默认加载java安装目录/jre/lib/ext下的文件
通过扩展类加载器去加载用户jar包有以下方式:
-
放入/jre/lib/ext下进行扩展。不推荐,尽可能不要去更改JDK安装目录中的内容。
-
使用参数进行扩展。推荐,使用-Djava.ext.dirs=jar包目录 进行扩展,这种方式会覆盖掉原始目录,可以用;(windows):(macos/linux)追加上原始目录
5.application应用程序类加载器
主要加载java程序中的类和maven引入第三方依赖库中的类,加载的是classpath下的类文件
二.双亲委派机制
1.初识双亲委派机制
双亲委派机制指的是当一个类加载器接收到类加载任务时,会自底向上查看类有没有被加载过,然后向下尝试加载类。
每个类加载器都有父类加载器,启动类加载器除外,扩展类加载器的父类加载器是null。
在向上查看的过程中发现类以及被加载过了咋直接返回。
如果到达启动类加载器依然没有类加载过,则启动类加载器尝试加载该类,如果加载成功则返回,加载失败则交给下级加载器。
2.双亲委派机制的作用
1.确保类加载的安全性。
2.避免重复加载
3.为什么要打破双亲委派机制
比如tomcat服务器里可能有两个web应用的类名相同,那么tomcat服务器就必须分别加载这两个类名,此时就需要打破双亲委派机制。
4.如何打破双亲委派机制
1.自定义类加载器
首先介绍CassLoader四个核心方法
public Class<?> loadClass(String name)
类加载的入口,提供了双亲委派机制。内部会调用findClass 重要
protected Class<?> findClass(String name)
由类加载器子类实现,获取二进制数据调用defineClass ,比如URLClassLoader会根据文件路径去获取类文件中的二进制数据。重要
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
做一些类名的校验,然后调用虚拟机底层的方法将字节码信息加载到虚拟机内存中
protected final void resolveClass(Class<?> c)
执行类生命周期中的连接阶段
loadclass方法是加载类的入口,提供了双亲委派机制,里面调用findcalss方法
findclass方法是获取二进制数据,并调用defineclass方法
defineclass方法是调用本地接口将字节码信息保存到虚拟机内存中
resolveclass 是执行连接阶段
打破双亲委派机制显而意见重写loadclass方法即可
那么我们欣赏一下loadclass的源代码
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
//加锁,避免多线程类加载器重复加载同一个类
synchronized (getClassLoadingLock(name)) {
//根据全限定名查找该类是否已加载,没有加载返回null,加载则返回该类
Class<?> c = findLoadedClass(name);
//类没有加载,进入if语句
if (c == null) {
long t0 = System.nanoTime();
try {
如果有父类加载器则上抛,直到到达启动类加载器
if (parent != null) {
c = parent.loadClass(name, false);
} else {
//启动类加载器尝试加载该类
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
//进行回溯,自顶到下尝试加载
if (c == null) {
long t1 = System.nanoTime();
c = findClass(name);
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
我们只要删除双亲委派机制的逻辑即可
直接根据类名读取二进制数据,然后调用define方法保存到虚拟机内存中即可
2.线程上下文类加载器
我们通过分析JDBC案例去分析线程上下文加载器如何打破双亲委派机制
首先通过Bookstrap启动类加载器去加载DriverManager驱动管理类,然后驱动管理类去通过spi机制暴漏出驱动类接口实现类的全限定名,并通过获取线程上下文类加载器获取应用程序类加载器,然后通过该加载器加载驱动类。
那么我们分析一个问题,JDBC案例真的打破了双亲委派机制了吗?
我认为没有。
因为类不是在一开始就同时加载完成的,JDBC案例首先加载了DriverManager,然后调用next方法在DriverManager类的初始化阶段去加载驱动类。这并没有打破双亲委派机制。
所以我认为打破双亲委派机制仅仅只能通过重写load方法。
3.Osgi框架的类加载器
历史上,OSGi模块化框架。它存在同级之间的类加载器的委托加载。OSGi还使用类加载器实现了热部署的功能。热部署指的是在服务不停止的情况下,动态地更新字节码文件到内存中。