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,一经查实,立即删除!

相关文章

JDK源码解析之 Java.lang.Package

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

Docker入门-架构

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

Docker原理之Namespaces

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

Docker原理之CGroups

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

Docker原理之UnionFS

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

Docker使用-构建MySQL

拉取官方镜像&#xff08;我们这里选择5.7&#xff0c;如果不写后面的版本号则会自动拉取最新版&#xff09; docker pull mysql:5.7 # 拉取 mysql 5.7 docker pull mysql # 拉取最新版mysql镜像MySQL文档地址 检查是否拉取成功 $ sudo docker images一般来说数据库容…

Java集合:什么是Java集合?

一、集合的由来 通常&#xff0c;我们的Java程序需要根据程序运行时才知道创建了多少个对象。但若非程序运行&#xff0c;程序开发阶段&#xff0c;我们根本不知道到底需要多少个数量的对象&#xff0c;甚至不知道它的准确类型。为了满足这些常规的编程需要&#xff0c;我们要…

Java集合:Map集合

一、简述 public interface Map<K,V>将键映射到值的对象。一个映射不能包含重复的键&#xff1b;每个键最多只能映射到一个值。 注意&#xff1a;Map中的集合不能包含重复的键&#xff0c;值可以重复。每个键只能对应一个值。 Map集合是键值对形式存储值的&#xff0c…

用离线编辑器Zoundry写zblog日志

Zoundry是免费的离线网志发布工具&#xff0c;由于家里的网络很差&#xff0c;写了一半的日志经常因为掉线而丢失&#xff0c;这样一款软件的确是很必要的。今天下载试用了一下&#xff0c;感觉的确不错。使用起来也很简单&#xff1a; 1.下载并安装zoundry软件&#xff1a;现…

Flume简单介绍

在一个完整的离线大数据处理系统中&#xff0c;除了HDFSMapReduceHive组成分析系统的核心之外&#xff0c;还需要数据采集、结果数据导出、任务调度等不可或缺的辅助系统&#xff0c;而这些辅助工具在hadoop生态体系中都有便捷的开源框架&#xff0c;在此&#xff0c;我们首先来…

Java并发篇_线程详解

线程&#xff08;thread&#xff09; 是操作系统能够进行运算调度的最小单位。它被包含在进程之中&#xff0c;是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流&#xff0c;一个进程中可以并发多个线程&#xff0c;每条线程并行执行不同的任务。 一、线程的…

Spark-大规模数据处理计算引擎

官网&#xff1a;http://spark.apache.org 一、Spark是什么 Spark是一种快速、通用、可扩展的大数据分析引擎&#xff0c;2009年诞生于加州大学伯克利分校AMPLab&#xff0c;2010年开源&#xff0c;2013年6月成为Apache孵化项目&#xff0c;2014年2月成为Apache顶级项目。项目是…

CentOS7下Spark集群的安装

从物理部署层面上来看&#xff0c;Spark主要分为两种类型的节点&#xff0c;Master节点和Worker节点&#xff0c;Master节点主要运行集群管理器的中心化部分&#xff0c;所承载的作用是分配Application到Worker节点&#xff0c;维护Worker节点&#xff0c;Driver&#xff0c;Ap…

Scala变量和常用数据类型

一、 声明值和变量 Scala声明变量有两种方式&#xff0c;一个用val&#xff0c;一个用var。 声明方式&#xff1a;val / var 变量名 : 变量类型 变量值 val定义的值是不可变的&#xff0c;它不是一个常量&#xff0c;是不可变量&#xff0c;或称之为只读变量。 val示例&am…

(转)JVM监控工具介绍

2008年03月04日 16:57原作者&#xff1a; stone2083 原文地址&#xff1a;http://www.blogjava.net/stone2083/archive/2008/02/25/182081.htmljstatd启动jvm监控服务。它是一个基于rmi的应用&#xff0c;向远程机器提供本机jvm应用程序的信息。默认端口1099。实例&#xff1a;…

【Kubernetes】控制器Statefulset

Statefulset控制器 一、概念二、Statefulset资源清单文件编写技巧2.1、查看定义Statefulset资源需要的字段2.2、查看statefulset.spec字段如何定义2.3、查看statefulset的spec.template字段如何定义 三、Statefulset使用案例&#xff1a;部署web站点3.1、编写一个Statefulset资…

Java并发篇_Java内存模型

在并发编程中&#xff0c;我们通常会遇到以下三个问题&#xff1a;原子性问题&#xff0c;可见性问题&#xff0c;有序性问题。那么它们产生的原因和在Java中解决的办法又是什么呢&#xff1f; 一、内存模型的相关概念 ​ 计算机在执行程序时&#xff0c;每条指令都是在CPU中执…

Java并发篇_synchronized

synchronized是Java语言的关键字&#xff0c;当它用来修饰一个方法或者一个代码块的时候&#xff0c;能够保证在同一时刻最多只有一个线程执行该段代码。本文给大家介绍java中的用法。 一、为什么要使用synchronized 在并发编程中存在线程安全问题&#xff0c;主要原因有&…

Java并发篇_volatile

volatile是Java提供的一种轻量级的同步机制。Java 语言包含两种内在的同步机制&#xff1a;同步块&#xff08;或方法&#xff09;和 volatile 变量&#xff0c;相比于synchronized&#xff08;synchronized通常称为重量级锁&#xff09;&#xff0c;volatile更轻量级&#xff…

分布式文件系统:原理、问题与方法

本地文件系统如ext3&#xff0c;reiserfs等&#xff08;这里不讨论基于内存的文件系统&#xff09;&#xff0c;它们管理本地的磁盘存储资源、提供文件到存储位置的映射&#xff0c;并抽象出一套文件访问接口供用户使用。但随着互联网企业的高速发展&#xff0c;这些企业对数据…