深入理解Java类加载器(一):Java类加载原理解析

protected Class<?> findClass(String name) throws ClassNotFoundException { … }

//定义类型,一般在findClass方法中读取到对应字节码后调用,final的,不能被继承

//这也从侧面说明:JVM已经实现了对应的具体功能,解析对应的字节码,产生对应的内部数据结构放置到方法区,所以无需覆写,直接调用就可以了)

protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError{ … }

通过进一步分析标准扩展类加载器和系统类加载器的代码以及其公共父类(java.net.URLClassLoader和java.security.SecureClassLoader)的代码可以看出,都没有覆写java.lang.ClassLoader中默认的 加载委派规则 — loadClass(…)方法。既然这样,我们就可以从java.lang.ClassLoader中的loadClass(String name)方法的代码中分析出虚拟机默认采用的双亲委派机制到底是什么模样:

public Class<?> loadClass(String name) throws ClassNotFoundException {

return loadClass(name, false);

}

protected Class<?> loadClass(String name, boolean resolve)

throws ClassNotFoundException

{

synchronized (getClassLoadingLock(name)) {

//首先判断该类型是否已经被加载

Class<?> c = findLoadedClass(name);

if (c == null) {

//如果没有加载,就委托给父类加载器或者委派给启动类加载器加载

long t0 = System.nanoTime();

try {

if (parent != null) {

//如果存在父类加载器,就委派给父类加载器加载

c = parent.loadClass(name, false);

} else {//递归终止条件

// 由于启动类加载器无法被Java程序直接引用,因此默认用 null 替代

// parent == null就意味着由启动类加载器尝试加载该类,

// 即通过调用 native方法 findBootstrapClass0(String name)加载

c = findBootstrapClassOrNull(name);

}

} catch (ClassNotFoundException e) {

// ClassNotFoundException thrown if class not found

// from the non-null parent class loader

}

if (c == null) {

// 如果父类加载器不能完成加载请求时,再调用自身的findClass方法进行类加载,

// 若加载成功,findClass方法返回的是defineClass方法的返回值

// 注意,若自身也加载不了,会产生ClassNotFoundException异常并向上抛出

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©;

}

return c;

}

}

通过上面的代码分析,我们可以对JVM采用的双亲委派类加载机制有了更感性的认识,下面我们就接着分析一下启动类加载器、标准扩展类加载器和系统类加载器三者之间的关系。可能大家已经从各种资料上面看到了如下类似的一幅图片:

在这里插入图片描述

上面图片给人的直观印象是:系统类加载器的父类加载器是标准扩展类加载器,标准扩展类加载器的父类加载器是启动类加载器,下面我们就用代码具体测试一下:

public class Loadertest

{

public static void main(String[] args)

{

System.out.println(ClassLoader.getSystemClassLoader());

System.out.println(ClassLoader.getSystemClassLoader().getParent());

System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());

}

}

输出的结果是:

在这里插入图片描述

通过以上的代码输出,我们知道:通过java.lang.ClassLoader.getSystemClassLoader()可以直接获取到系统类加载器 ,并且可以判定系统类加载器的父加载器是标准扩展类加载器,但是我们试图获取标准扩展类加载器的父类加载器时却得到了null。事实上,由于启动类加载器无法被Java程序直接引用,因此JVM默认直接使用 null 代表启动类加载器。我们还是借助于代码分析一下,首先看一下java.lang.ClassLoader抽象类中默认实现的两个构造函数:

protected ClassLoader() {

this(checkCreateClassLoader(), getSystemClassLoader());

}

private ClassLoader(Void unused, ClassLoader parent) {

this.parent = parent;

if (ParallelLoaders.isRegistered(this.getClass())) {

parallelLockMap = new ConcurrentHashMap<>();

package2certs = new ConcurrentHashMap<>();

domains =

Collections.synchronizedSet(new HashSet());

assertionLock = new Object();

} else {

// no finer-grained lock; lock on the classloader instance

parallelLockMap = null;

package2certs = new Hashtable<>();

domains = new HashSet<>();

assertionLock = this;

}

}

紧接着,我们再看一下ClassLoader抽象类中parent成员的声明:

// The parent class loader for delegation

private ClassLoader parent;

声明为私有变量的同时并没有对外提供可供派生类访问的public或者protected设置器接口(对应的setter方法),结合前面的测试代码的输出,我们可以推断出:

1.系统类加载器(AppClassLoader)调用ClassLoader(ClassLoader parent)构造函数将父类加载器设置为标准扩展类加载器(ExtClassLoader)。(因为如果不强制设置,默认会通过调用getSystemClassLoader()方法获取并设置成系统类加载器,这显然和测试输出结果不符。)

2.扩展类加载器(ExtClassLoader)调用ClassLoader(ClassLoader parent)构造函数将父类加载器设置为null(null 本身就代表着引导类加载器)。(因为如果不强制设置,默认会通过调用getSystemClassLoader()方法获取并设置成系统类加载器,这显然和测试输出结果不符。)

事实上,这就是启动类加载器、标准扩展类加载器和系统类加载器之间的委派关系。

3、类加载双亲委派示例


以上已经简要介绍了虚拟机默认使用的启动类加载器、标准扩展类加载器和系统类加载器,并以三者为例结合JDK代码对JVM默认使用的双亲委派类加载机制做了分析。下面我们就来看一个综合的例子,首先在IDE中建立一个简单的java应用工程,然后写一个简单的JavaBean如下:

package com.huawei.classload001;

public class TestBean {

public TestBean() {

}

}

在现有当前工程中另外建立一个测试类(ClassLoaderTest.java)内容如下:

测试一:

package com.huawei.classload001;

public class ClassLoaderTest {

public static void main(String[] args) {

try {

//查看当前系统类路径中包含的路径条目

System.out.println(System.getProperty(“java.class.path”));

//调用加载当前类的类加载器(这里即为系统类加载器)加载TestBean

Class typeClassLoad = Class.forName(“com.huawei.classload001.TestBean”);

//查看被加载的TestBean类型是被哪个类加载器加载的

System.out.println(typeClassLoad.getClassLoader());

}

catch (ClassNotFoundException e) {

e.printStackTrace();

}

}

}

打印结果:

“C:\Program Files\Java\jdk1.8.0_91\jre\lib\charsets.jar;

C:\Program Files\Java\jdk1.8.0_91\jre\lib\deploy.jar;

C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\access-bridge-64.jar;

C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\cldrdata.jar;

C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\dnsns.jar;

C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\jaccess.jar;

C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\jfxrt.jar;

C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\localedata.jar;

C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\nashorn.jar;

C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\sunec.jar;

C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\sunjce_provider.jar;

C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\sunmscapi.jar;

C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\sunpkcs11.jar;

C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\zipfs.jar;

C:\Program Files\Java\jdk1.8.0_91\jre\lib\javaws.jar;

C:\Program Files\Java\jdk1.8.0_91\jre\lib\jce.jar;

C:\Program Files\Java\jdk1.8.0_91\jre\lib\jfr.jar;

C:\Program Files\Java\jdk1.8.0_91\jre\lib\jfxswt.jar;

C:\Program Files\Java\jdk1.8.0_91\jre\lib\jsse.jar;

C:\Program Files\Java\jdk1.8.0_91\jre\lib\management-agent.jar;

C:\Program Files\Java\jdk1.8.0_91\jre\lib\plugin.jar;

C:\Program Files\Java\jdk1.8.0_91\jre\lib\resources.jar;

C:\Program Files\Java\jdk1.8.0_91\jre\lib\rt.jar;

D:\masterSpring\jvm\target\classes;

D:\repo\sping\org\apache\commons\commons-lang\2.6\commons-lang-2.6.jar” com.huawei.classload001.ClassLoaderTest

C:\Program Files\Java\jdk1.8.0_91\jre\lib\charsets.jar;

C:\Program Files\Java\jdk1.8.0_91\jre\lib\deploy.jar;

C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\access-bridge-64.jar;

C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\cldrdata.jar;

C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\dnsns.jar;

C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\jaccess.jar;

C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\jfxrt.jar;

C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\localedata.jar;

C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\nashorn.jar;

C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext\sunec.jar;
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

总结

虽然我个人也经常自嘲,十年之后要去成为外卖专员,但实际上依靠自身的努力,是能够减少三十五岁之后的焦虑的,毕竟好的架构师并不多。

架构师,是我们大部分技术人的职业目标,一名好的架构师来源于机遇(公司)、个人努力(吃得苦、肯钻研)、天分(真的热爱)的三者协作的结果,实践+机遇+努力才能助你成为优秀的架构师。

如果你也想成为一名好的架构师,那或许这份Java成长笔记你需要阅读阅读,希望能够对你的职业发展有所帮助。

image

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
alt=“img” style=“zoom: 33%;” />

总结

虽然我个人也经常自嘲,十年之后要去成为外卖专员,但实际上依靠自身的努力,是能够减少三十五岁之后的焦虑的,毕竟好的架构师并不多。

架构师,是我们大部分技术人的职业目标,一名好的架构师来源于机遇(公司)、个人努力(吃得苦、肯钻研)、天分(真的热爱)的三者协作的结果,实践+机遇+努力才能助你成为优秀的架构师。

如果你也想成为一名好的架构师,那或许这份Java成长笔记你需要阅读阅读,希望能够对你的职业发展有所帮助。

[外链图片转存中…(img-p9Aq74kc-1713748403997)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 14
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值