文章目录
前言
- JVM 是如何加载一个类?
- 什么是双亲委派机制?
- 什么时候上级类无法加载?
类加载的分类
- 启动类加载器 bootstrap classloader :加载jre/lib/rt.jar
- 扩展类加载器 extension classloader :加载jre/lib/ext/*.jar
- 应用程序类加载器 application classloader:加载classpath上指定的类库
JVM 如何加载一个类
Launcher
类是java的入口,在启动java应用的时候会首先创建Launcher类,创建Launcher类的时候会准备应用程序运行中需要的类加载器。- Launcher作为JAVA应用的入口,根据双亲委派模型,Laucher是由JVM创建的,它类加载器应该是BootStrapClassLoader
Launcher-源码
public Launcher() {
Launcher.ExtClassLoader var1;
try {
//获取ExtCkassLoader
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
//获取AppClassLoader
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
}
ExtClassLoader 对象创建
- 可以看到 ExtClassLoader 是Launcher的一个内部类,而且是单例模式
static class ExtClassLoader extends URLClassLoader {
private static volatile Launcher.ExtClassLoader instance;
public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
if (instance == null) {
Class var0 = Launcher.ExtClassLoader.class;
synchronized(Launcher.ExtClassLoader.class) {
if (instance == null) {
//去创建对象
instance = createExtClassLoader();
}
}
}
return instance;
}
public ExtClassLoader(File[] var1) throws IOException {
//可以看到去调用父类的时候 classLoader为null,那也就说在调用,
super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
}
}
ExtClassLoader对象创建过程
- 通过上面源码分析,在创建对象的时候去调用了父类的构造函数,首先调用的是
URLClassLoader
public URLClassLoader(URL[] urls, ClassLoader parent,
URLStreamHandlerFactory factory) {
//将 加载器传递给了父类
super(parent);
//...code
}
protected SecureClassLoader(ClassLoader parent) {
super(parent);
//...code
}
protected ClassLoader(ClassLoader parent) {
this(checkCreateClassLoader(), parent);
}
//最终将 parent的赋值放到了这里,
private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent;
//code...
}
什么是双亲委派机制
- 当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,以此类推,这样所有的加载请求最终都会传到最顶层的启动类加载器, 如果上级的类加载器无法加载,自己才会去加载这个类。
- 需要说明的是,上级并不是我的父类,而是由类加载器创建对象的时候,传入的加载器,他们的构成是由类似于链表的结构,
代码体现
- 创建一个类,
- 添加一个main方法,作为程序入口
- 搜索
URLClassLoader
找到其子类AppClassLoader
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);
}
}
什么时候上级类加载器无法加载
- 需要注意的是findClass这个方法,也就是说当bootstrapClassLoader无法加载的时候,就会去调用findClass这个方法,需要注意的是,URLClassLoader重写了findClass这个方法,那也就是说
ExtClassLoader
是加载jar包中的内容,而我们的自己写的类则是由URLClassLoader.findClass()所加载
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);
if (c == null) {
long t0 = System.nanoTime();
try {
//判断 parent 如果是Null 则交给 `bootstrapClassLoader` 去处理
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
long t1 = System.nanoTime();
//这个由当前类去调用,比如如果当前的this是 App的话则会去调用父类的 findClass
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;
}
}
为什么要有双亲委派
防止内存出现多份同样的字节码,为什么这么说呢?
- BootstrapClassLoader 加载的是 java 的核心库
java.*
下的 jar包 - ExtClassLoader 加载的是 java 的扩展库
javax.*
下的jar包 - AppClassLoader 加载的是我们自己的 开发的 jar包
比如 java.* 下就有 String 这个 class 文件 时由
BootstrapClassLoader
加载到内存的为什么这么做呢?
假定我们开发人员 自定义了一个 String 查找顺序就是
- AppClassLoader 委托给 ExtClassLoader 委托给 BootstrapClassLoader 最后 BootstrapClassLoader 发现内存中已经有了 String 这个 就不允许在 加载了…
- 简单来就是为了 防止内存出现多份相同的字节码…也保证了安全性,防止核心 Class 被修改
最终说明
通过对上面源码的分析,其实双亲委派机制是由
- JVM创建一个
Launcher
作为程序入口 - 在创建Launcher的时候创建了ExtClassLoader加载器,也就是
Extension ClassLoader
- 然后然后创建了
AppClassLoader
加载器 - 那怎么体现双亲委派机制的呢?就是在调用
ClassLoader
的loadClass
方法的时候AppClassLoader
去调用loadClass()
的时候- 判断 parent 这个属性是不是null,如果是null则交由
bootstrapClassLoader
去处理 - 如果不是null则由
ExtClassLoader
去处理, - 创建
ExtClassLoader
的时候 parent是null 则交给bootstrapClassLoader
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);
if (c == null) {
long t0 = System.nanoTime();
try {
//判断 parent 如果是Null 则交给 `bootstrapClassLoader` 去处理
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//这个由当前类去调用,比如如果当前的this是 App的话则会去调用父类的 findClass
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;
}
}
图解
最终分析后可以得出双亲委派模型图如下(要注意这里的类加载器的父子关系不是以继承的关系实现的,而是通过组合的方式,即内部变量parent指向父加载器)