JDK源码解析之 java.lang.ClassLoader

Class代表它的作用对象是类,Loader代表它的功能是加载,那么ClassLoader就是把一个以.class结尾的文件以JVM能识别的存储形式加载到内存中。

一、核心方法

1、loadClass方法

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 {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();//findClass需要Overridec = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}}

一开始,先调用findLoadedClass(name)去检查该类是否已经被加载过了,加载过的类不会重复加载。如果是第一次加载(向上传递任务),分为两种:1、parent != null ;当前类加载器不是BootStrapClassLoader,直接调用父类加载器的loadClass来加载该类。2、parent == null 当前加载器是BootStrapClassLoader,则自己尝试加载该类。

如果上层的加载器都没有成功加载。(该类没有在上面任何类加载器的类命名空间下。)任务向下委托,最终回到最初接到加载任务的类加载器,调用findClass()来尝试加载该类。如果我们自定义一个ClassLoader,可以直接覆盖该方法(findClass)来进行定制加载逻辑

可以看出,只要每次优先调用parent.loadClass()来执行加载逻辑,就实现了双亲委派模型,同时发现,想破坏双亲委派模型也可以,覆盖loadClass方法,并且里面不调用parent.loadClass方法即可。

二、总结

JVM启动时产生三个类加载器:Bootstrap ClassLoader,Extension ClassLoader,App ClassLoader。

Bootstrap ClassLoader负责加载核心类库(jdk lib下的类),用C++实现,java代码中显示为null;Extension ClassLoader负责加载lib/ext下的类;App ClassLoader负责加载ClassPath下的类。

其中Bootstrap ClassLoader是Extention ClassLoader的父加载器,Extention ClassLoader是APP ClassLoader的父加载器,但不是父子类关系。 由源代码可见双亲委托机制在加载类时类似递归先回溯到Bootstrap ClassLoader,再Extension ClassLoader,再App ClassLoader…此机制一个重要原因是安全原因:防止不安全类的加载进来,比如重新实现String类Boolean类等,加载时回溯到Bootstrap ClassLoader会发现已经加载过了,在不会加载自定义的String类取代掉JDK String。判断两个类是不是同一个类,除了名字相同还要是加载器类相同才可以。

三、拓展

1、类加载过程

一个类在被使用之前,会经历class文件生成—>加载—>连接—>初始化等阶段。这些阶段组合起来为完整的类加载过程,其中加载阶段主要完成三件事:

  1. 通过类的全限定名来获取定义次类的二进制字节流
  2. 将该二进制字节流定义的静态数据结构转换成方法区的运行时数据结构
  3. 在内存中生成一个代表该类的Class对象,供外部通过该对象来获取类的元数据信息

上述类加载的大部分阶段是由JVM控制的,但JVM对于加载阶段有些没有做强制限制,比如从哪获取class文件,以及如何加载class都可以由用户自定义实现方案,正是由于JVM提供的这些入口,衍生出了动态字节码,applet, OSGI,类隔离(ali-Pandora)等技术。

正常情况下一个ClassLoader需要两个必要属性

1、parent:用于指明当前ClassLoader的父类加载器
2、url:类命名空间,用于指明当前ClassLoader从哪里加载class文件

2、ClassLoader分类:

1、系统自带的ClassLoader
2、用户自定义类加载ClassLoader

其中系统自带的ClassLoader分成三类:
1.1、 BootstrapClassloader: 启动类加载器, 加载JAVA_HOME/lib/目录下的所有jar包, 而该目录下的主要放系统核心类库,比如包含Object, String等类的rt.jar就是由该类加载器加载进内存的.
1.2、ExtClassloader: 扩展类加载器, 加载JAVA_HOME/jre/lib/ext/目录下的所有jar包或者由java.ext.dirs系统属性指定的jar包。放入这个目录下的jar包对所有AppClassloader都是可见的(双亲委派模型 ).
1.3、AppClassloader | SystemClassLoader: 应用类加载器, 也叫系统类加载器. 是我们平时接触最密切的类加载器, 主要负责加载classpath下的所有类. 平时我们用maven构建项目时, 添加进pom文件中的依赖, 最后都是由idea帮我们直接把依赖关联的jar包放到classPath下, 并在运行时, 由AppClassloader帮我们把这些jar包中的class加载到内存中.

总结下来,BootstrapClassloader和ExtClassLoader主要负责加载JDK相关的系统类。AppClassLoader来处理我们classpath下的所有类。

3、疑问:既然Class使用前需要ClassLoader加载到内存,那ClassLoader本身是由谁加载的?

BootStrapClassLoader并不是用java写的,而是用C++,他属于JVM的一部分,在JVM启动时就会被连带启动起来,所以在不需要被某一个ClassLoader加载。ExtClassLoader和AppClassLoader是定义在rt.jar中,所以是有BootStrapClassLoader加载进来的。

4、疑问:一个类什么时候被加载:

简单来说,程序第一次使用某个类的时候,就会调用ClassLoader去加载该类。比如:new 一个类的实例对象或者使用反射Class.forName(…)来获取Class对象(类对象)都会触发类加载。

5、疑问:ClassLoader有很多种,我们仅知道他们都有各自的加载路径,而它们是什么关系,如何协作配合的呢?(双亲委派模型是什么):

双亲委派模型图

要理解双亲委派模型,首先要了解各个类加载器的层次关系。如图,从下往上看,每个类的parent是在它上方与它相邻的类加载器,比如最下面的CustomClassLoader代表用户自定义类加载器,它的parent是AppClassLoader,AppClassLoader的parent是ExtClassLoader,ExtClassLoader的parent是BootStrapClassLoader,这种递推关系到BootStrapClassLoader终止,因为它是顶层类加载器,它没有parent。

注意:这种层次关系是组合形式来实现的,并不是传统理解的继承,如:CustomClassLoader的parent是AppClassLoader,但CustomClassLoader并不是AppClassLoader的子类,仅仅是把自己类中的parent属性指向AppClassLoader

6、疑问:当需要加载一个类是,各个类加载器它们是如何协同工作的呢?

当一个类加载器想要加载一个类时,它会先把该加载任务委托给它的父类加载器(parent),而不是自己先尝试加载,以此类推,父类加载器又会委托自己的父类加载器去执行加载任务,直到最顶层的BootStrapClassLoader为止,如果BootStrapClassLoader在自己类空间(上面提到的URL)找到了该类的Class文件,就会加载该类到内存中,如果找不到,BootStrapClassLoader会把任务向下传递回ExtClassLoader,让它去尝试加载该类,如果ExtClassLoader在自己的类空间中找到该类的class文件则会加载该类到内存中,如果找不到,则ExtClassLoader会把任务继续向下传递,以此类推,直到某个类加载器在自己的类命名空间中找到该类的Class文件,并加载到内存中

可以看出,从下往上传递任务,秉着优先让上层加载器去加载的原则,而从上到下传递任务,秉着优先让自己去加载的原则

好处:可以让每个类具有优先级层次,越靠上方的类加载器加载进来的类层级就会越高,在程序中可见的范围就会越大。如Object类,因为处于rt.jar中,根据双亲委派模型的执行规律,会被最上面的BootStrapClassLoader加载进来,也就能保证该类在程序中只会被加载一次,也只会存在一份,所有的程序使用的都是这一份,不会出现自己定义一个Object类,被自己的ClassLoader给加载进来的情况,因为rt.jar中的Object会被优先加载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/535735.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux Vi的使用

一、插入文本┌──┬────────────┐│命令│描述 │├──┼────────────┤│i │在当前字符前插入文本 │├──┼────────────┤│I │在行首插入文本 │├──┼────────────┤│a │在当前字符后添加文本 │├──┼──…

Hive-beeline服务

Hive客户端工具后续使用了Beeline 替代HiveCLI &#xff0c;并且后续版本也会废弃掉HiveCLI 客户端工具,Beeline是 Hive 0.11版本引入的新命令行客户端工具,它是基于SQLLine CLI的JDBC客户端。 Beeline支持嵌入模式(embedded mode)和远程模式(remote mode)。在嵌入式模式下&am…

用户账号管理基本概念

什么是用户账号管理用户账号一般包括普通用户账号、管理账号和系统账号。为了鉴别用户身份以及加强系统安全&#xff0c;系统为每个使用它的人分配了一个账号&#xff0c;这就是普通用户账号。每个人拥有一个独立的普通用户账号&#xff0c;每个账号有不同的用户名和密码。用户…

JDK源码解析之 Java.lang.Compiler

Compiler类提供支持Java到本机代码编译器和相关服务。在设计上&#xff0c;它作为一个占位符在JIT编译器实现。 一、源码部分 public final class Compiler {private Compiler() {} // dont make instancesprivate static native void initialize();private st…

shell的基本概念

Shell就像一个壳层&#xff0c;这个壳层介于用户和操作系统之间&#xff0c;负责将用户的命令解释为操作系统可以接收的低级语言&#xff0c;并将操作系统响应的信息以用户可以了解的方式来显示。 从用户登陆到注销期间&#xff0c;用户输入的每个命令都会经过解译及…

JDK源码解析之 java.lang.System

一个和系统环境进行交互的类. System不允许被实例化, 而且是一个final类 一、不能实例化 private System() { }二、成员变量 public final static InputStream in null; //这是“标准”输入流。 public final static PrintStream out null; //这是“标准”输出流。 public …

详解MySQL中DROP,TRUNCATE 和DELETE的区别

注意:这里说的delete是指不带where子句的delete语句 相同点: truncate和不带where子句的delete, 以及drop都会删除表内的数据 不同点: 1. truncate和 delete只删除数据不删除表的结构(定义) drop语句将删除表的结构被依赖的约束(constrain),触发器(trigger),索引(index…

JDK源码解析之 Java.lang.Package

如果我们在Class对象上调用getPackage方法&#xff0c;就可以得到描述该类所在包的Package对象(Package类是在java.lang中定义的)。我们也可以用包名通过调用静态方法getPackage或者调用静态方法getPackages(该方法返回由系统中所有已知包构成的数组)来获得Package对象。getNam…

Mysql中limit的用法详解

在我们使用查询语句的时候&#xff0c;经常要返回前几条或者中间某几行数据&#xff0c;这个时候怎么办呢&#xff1f;不用担心&#xff0c;mysql已经为我们提供了这样一个功能。SELECT * FROM table LIMIT [offset,] rows | rows OFFSET offset LIMIT 子句可以被用于强制 SE…

Docker入门-简介

独具魅力的Docker作为一门新技术&#xff0c;它的出现有可能引起其所在领域大范围的波动甚至是重新洗牌。根据业内专业人士的看法&#xff0c;不论如何&#xff0c;Docker的出现&#xff0c;已经成为云服务市场中一枚极具意义的战略性棋子。从2013年开始在国内发力&#xff0c;…

Mysql中limit的优化

在一些情况中&#xff0c;当你使用LIMIT row_count而不使用HAVING时&#xff0c;MySQL将以不同方式处理查询。 如果你用LIMIT只选择一些行&#xff0c;当MySQL选择做完整的表扫描时&#xff0c;它将在一些情况下使用索引。 如果你使用LIMIT row_count与ORD…

Docker入门-架构

Docker 包括三个基本概念: 镜像&#xff08;Image&#xff09;&#xff1a;Docker 镜像&#xff08;Image&#xff09;&#xff0c;就相当于是一个 root 文件系统。比如官方镜像 ubuntu:16.04 就包含了完整的一套 Ubuntu16.04 最小系统的 root 文件系统。容器&#xff08;Cont…

MYSQL出错代码列表大全(中文)

mysql出错了,以前往往靠猜.现在有了这张表,一查就出来了. 1005&#xff1a;创建表失败1006&#xff1a;创建数据库失败1007&#xff1a;数据库已存在&#xff0c;创建数据库失败1008&#xff1a;数据库不存在&#xff0c;删除数据库失败1009&#xff1a;不能删除数据库文件导致…

Docker入门-安装

Centos7下安装Docker docker官方说至少Linux 内核3.8 以上&#xff0c;建议3.10以上&#xff08;ubuntu下要linux内核3.8以上&#xff0c; RHEL/Centos 的内核修补过&#xff0c; centos6.5的版本就可以&#xff09; 1、把yum包更新到最新&#xff1a;yum update 2、安装需要的…

Docker原理之Namespaces

命名空间&#xff08;namespaces&#xff09;是 Linux 为我们提供的用于分离进程树、网络接口、挂载点以及进程间通信等资源的方法。 一、Namespaces 在日常使用 Linux 或者 macOS 时&#xff0c;我们并没有运行多个完全分离的服务器的需要&#xff0c;但是如果我们在服务器上启…

mysql 快速插入(insert)多条记录

方法1: INSERT INTO table(col_1, col_2,col_3) VALUES(1,11,111); INSERT INTO table(col_1, col_2,col_3)   VALUES(2,22,222); INSERT INTO table(col_1, col_2,col_3)   VALUES(3,33,333); 有没有更快捷的办法呢?答案是有(见方法2) 方法2: INSERT INTO table(col…

Docker原理之CGroups

控制组&#xff08;cgroups&#xff09;是 Linux 内核的一个特性&#xff0c;主要用来对共享资源进行隔离、限制、审计 等。只有能控制分配到容器的资源&#xff0c;才能避免当多个容器同时运行时的对系统资源的竞争。控制组技术最早是由 Google 的程序员 2006 年起提出&#x…

Mysql中的转义字符

字符串是多个字符组成的一个字符序列&#xff0c;由单引号( “”) 或双引号 ( “"”) 字符包围。(但在 ANSI 模式中运行时只能用单引号)。 例如&#xff1a; a string"another string"在一个字符串中&#xff0c;如果某个序列具有特殊的含义&#xff0c;每个序…

Docker原理之UnionFS

一、UnionFS Linux 的命名空间和控制组分别解决了不同资源隔离的问题&#xff0c;前者解决了进程、网络以及文件系统的隔离&#xff0c;后者实现了 CPU、内存等资源的隔离&#xff0c;但是在 Docker 中还有另一个非常重要的问题需要解决 - 也就是镜像。 镜像到底是什么&#…

教你精确编写高质量高性能的MySQL语法

在应用系统开发初期&#xff0c;由于开发数据库数据比较少&#xff0c;对于查询SQL语句&#xff0c;复杂视图的编写&#xff0c;刚开始不会体会出SQL语句各种写法的性能优劣&#xff0c;但是如果将应用系统提交实际应用后&#xff0c;随着数据库中数据的增加&#xff0c;系统的…