不管你是跟同事、同窗、上下级、同行、或者面试官讨论技术问题的时候,很容易卷入JVM大型撕逼现场。为了可以让你们从大型撕逼现场中脱颖而出,最近我苦思冥想如何把知识点尽量呈现的容易理解,方便记忆。因而就开启了这一系列文章的编写。为了让JVM相关知识点可以造成一个体系,arthinking将编写整理一系列的专题,以尽可能以图片的方式描述相关知识点,而且最终把全部相关知识点串成了一张图。持续更新中,欢迎你们阅读。有任何错落之处也请您高抬贵手帮忙指正,感谢!html
类加载器: 能够实现经过一个类的全限定名称来获取描述此类的二进制字节流。实现这个动做的代码模块成为”类加载器“。java
经过自定义类加载器能够实现各类有趣而强大的功能更:OSGi,热部署,代码加密等。web
如上图为类加载器的加载流程。面试
这里简单描述下:算法
**启动类加载器:**系统启动的时候,首先会经过由C++实现的启动类加载器,加载<JAVA_HOME>/lib目录下面的jar包,或者被-Xbootclasspath参数指定的路径而且被虚拟机识别的文件名的jar包。把相关Class加载到方法区中。spring
这一步会加载关键的一个类:sun.misc.Launcher
。这个类包含了两个静态内部类:数据库
能够反编译rt.jar文件查看详细代码:编程
![]()
![]()
在加载到Launcher类完成后,会对该类进行初始化,初始化的过程当中,会建立 ExtClassLoader 和 AppClassLoader,源码以下:bootstrap
public Launcher() {
ExtClassLoader extClassLoader;
try {
extClassLoader = ExtClassLoader.getExtClassLoader();
} catch (IOException iOException) {
throw new InternalError("Could not create extension class loader", iOException);
}
try {
this.loader = AppClassLoader.getAppClassLoader(extClassLoader);
} catch (IOException iOException) {
throw new InternalError("Could not create application class loader", iOException);
}
Thread.currentThread().setContextClassLoader(this.loader);
...
复制代码
因为启动类加载器是由C++实现的,因此在Java代码里面是访问不到启动类加载器的,若是尝试经过String.class.getClassLoader()
获取启动类的引用,会返回null
;后端
问题:
启动类加载器,扩展类加载器和应用类加载器都是又谁加载的?
- 启动类加载器是JVM的内部实现,在JVM申请好内存以后,由JVM建立这个启动类加载器
- 扩展类加载器和应用程序类加载器是由启动类加载器加载进来的;
说说如下代码输出什么:
public static void main(String[] args) { System.out.println("加载当前类的加载器:" + TestClassLoader.class.getClassLoader()); System.out.println("加载应用程序类加载器的加载器" + TestClassLoader.class.getClassLoader().getClass().getClassLoader()); System.out.println("String类的启动类加载器" + String.class.getClassLoader()); } 复制代码
如上图,扩展类加载器负责加载<JAVA_HOME>/lib/ext目录下或者被java.ext.dirs系统变量指定的路径中的类。
引用程序类加载器加载用户类路径下制定的类库,若是应用程序没有自定义过本身的类加载器,此类加载器就是默认的类加载器。
引用程序类加载器也叫系统类加载器,能够经过getSystemClassLoader
方法获得应用程序类加载器。
注意,如上图经过以上三个类加载器加载类到方法区以后,方法区中分别对应有各自的类信息存储区。不一样类加载器加载的同一个类文件不相等。
双亲委派模型在JDK1.2以后被引入,并普遍使用,这不是一个强制性的约束模型,二货思Java设计者推荐给开发者的一种类加载器实现方式。咱们也能够覆盖对应的方式,实现本身的加载模型。
类加载器的双亲委派机制以下:
也就是说:
问题:
- 为何要有这么复杂的双亲委派机制?
- 若是没有这种机制,咱们就能够篡改启动类加载器中须要的类了,如,修本身编写一个
java.lang.Object
用本身的类加载器进行加载,系统中就会存在多个Object类,这样Java类型体系最基本的行为也就没法保证了。
JDK中默认的双亲委派处理流程是怎么的呢?接下来咱们看看代码,如下是java.lang.ClassLoader.loadClass()
方法的实现:
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 {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} 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;
}
}
复制代码
转成流程图,便是:
如山图因此,老是先回尝试让父类加载器先加载,其次判断启动类加载器是否已经加载了,最后才尝试从当前类加载器加载。转换为更清晰的模型以下:
双亲委派模型具备如下特色:
启动类加载器,扩展类加载器,应用程序类加载器,他们分别管理者各自方法区里的一个区块。
根据上一篇文章咱们知道,方法区里面主要存储的是类的运行时数据结构,这个类的在方法区中的各类数据结构信息经过类的Class实例进行访问。
以下图:
方法区里面存储着加载进来的类信息,方法区同时雇佣了两类工种帮忙干活:
另外,方法区里面,启动类加载器类信息对扩展两类加载器类信息可见,而前面二者的类信息又对应用程序类加载器类信息可见。
在JDK1.0已经存在了ClassLoader类,可是当时尚未双亲委派机制,用户为了自定义类加载器,须要从新loadClass()
方法,而咱们知道,在JDK1.2之后,loadClass里面就是双亲委派机制的实现代码,此时,要实现自定义类加载器,须要从新findClass()类便可。
若是从新了loadClass()方法,也就意味着再也不遵循双亲委派模型了。
为何须要这个东西呢,咱们仍是从一个案例来讲起。
Tomcat中的类加载器
咱们知道Tomcat目录结构中有如下目录:
/common/: 该目录下的类库可被Tomcat和全部的WebApp共同使用;
/server/: 该目录下的类库可被Tomcat使用,但对全部的WebApp不可见;
/shared/: 该目录下的类库可被全部的WebApp共同使用,但对Tomcat本身不可见;
另外Web应用程序还有自身的类库,放在/WebApp/WEB-INF
目录中:这里面的类库仅仅能够被此Web应用程序使用,对Tomcat和其余Web应用程序都不可见。 为了实现以上各个目录的类库可见性效果,Tomat提供了以下的自定义类加载器:
如今以下场景:
咱们发现Tomcat下面有若干个webapp,每一个webapp都用到了spring,因而咱们把spring的jar包放到了shared
目录中。
因而问题出现了:因为spring的jar包是由Shared类加载器加载的,假设咱们要使用SpringContext的getBean方法,获取webapp中的Bean,若是是按照双亲委派模型,就会有问题了,由于webapp中的Java类是对SharedClassLoader不可见的:
Spring中的线程上下文类加载器
为了解决这个问题,Spring使用了线程上下文类加载器,即从ThreadLocal中获取到当前线程的上下文类加载器,来加载全部的类库和类。
关于Spring初始化源码相关解读,参考个人这边文章:Spring IoC原理剖析。
Spring中的bean类加载器
ApplicationContext中有一个beanClassLoader
字段,这个是bean的类加载器,在prepareBeanFactory()方法中作了初始化:
beanFactory.setBeanClassLoader(getClassLoader());
复制代码
getClassLoader方法以下:
@Override
@Nullable
public ClassLoader getClassLoader() {
return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
}
复制代码
ClassUtils.getDefaultClassLoader()方法:
@Nullable
public static ClassLoader getDefaultClassLoader() {
ClassLoader cl = null;
try {
cl = Thread.currentThread().getContextClassLoader();
}
catch (Throwable ex) {
// Cannot access thread context ClassLoader - falling back...
}
if (cl == null) {
// No thread context class loader -> use class loader of this class.
cl = ClassUtils.class.getClassLoader();
if (cl == null) {
// getClassLoader() returning null indicates the bootstrap ClassLoader
try {
cl = ClassLoader.getSystemClassLoader();
}
catch (Throwable ex) {
// Cannot access system ClassLoader - oh well, maybe the caller can live with null...
}
}
}
return cl;
}
复制代码
能够发现,这里最终取了当前线程上下文中的ClassLoader。
加载Bean
咱们来看看Spring加载Class的代码。这里咱们直接找到实例化Singletons的方法跟进去找须要关注的代码:
咱们发如今加载Bean Class的时候调用了这个方法:
AbstractBeanFactory:
ClassLoader beanClassLoader = getBeanClassLoader();
复制代码
也就是用到了ApplicationContext中的beanClassLoader
,线程上下文类加载器来加载Bean Class实例。
总结
Spring做为一个第三方类库,可能被任何的ClassLoader加载,因此最灵活的方式是直接使用上下文类加载器。
主要是相似OSGi这类的模块化热部署技术。在OSGi中再也不是双亲委派模型中的树状结构,而是更复杂的网状结构。
Where are static methods and static variables stored in Java?
《深刻理解Java虚拟机-JVM高级特性与最佳实践》
Chapter 5. Loading, Linking, and Initializing
本文为arthinking
基于相关技术资料和官方文档撰写而成,确保内容的准确性,若是你发现了有何错漏之处,烦请高抬贵手帮忙指正,万分感激。
你们能够关注个人博客:itzhai.com
获取更多文章,我将持续更新后端相关技术,涉及JVM、Java基础、架构设计、网络编程、数据结构、数据库、算法、并发编程、分布式系统等相关内容。
若是您以为读完本文有所收获的话,能够关注
个人帐号,或者点赞
的,您的支持就是我写做的动力!关注个人公众号,及时获取最新的文章。
本文做者: arthinking
博客连接: www.itzhai.com/jvm/what-is…
版权声明:
BY-NC-SA
许可协议:创做不易,如需转载,请务必附加上博客连接,谢谢!