类的生命周期中的第一步,就是要被 JVM 加载进内存,类加载器就是来干这件事。

一、类加载器种类

系统提供了 3 种类加载器:

1.启动类加载器(Bootstrap ClassLoader)
由 C 和 C++ 编写,是在 JVM 启动后初始化的。可在这里查看到源码(OpenJDK):http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/933f6b06c1cb/src/share/native/java/lang/ClassLoader.c
负责将存放在 <JAVA_HOME>\jre\lib 目录中的,或者被 -Xbootclasspath 参数所指定的路径中的,并且能被虚拟机识别的(仅按照文件名识别,如 rt.jar,名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中。 2.扩展类加载器(Extension ClassLoader)
由 sun.misc.Launcher$ExtClassLoader 实现,负责加载 <JAVA_HOME>\jre\lib\ext 目录中的所有类库,以及系统变量 java.ext.dirs 指定路径中的所有类库,开发者可以直接使用扩展类加载器。 3.应用程序类加载器(Application ClassLoader)
由 sun.misc.Launcher$AppClassLoader 实现,可以通过 ClassLoader 类中的 getSystemClassLoader() 方法的获得,所以一般也称它为“系统类加载器”。
它负责加载用户类路径(classpath:CLASSPATH 环境变量指定的, 由 -classpath 或 -cp 选项定义的,或者是 jar 中的 Manifest 的 classpath 属性定义的)上所指定的类库,以及系统变量 java.class.path 指定路径中的所有类库。
开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器(父子关系一般不会以继承的关系实现,而是以组合关系来复用父加载器的代码),结构如图:

getParent() 可获得父加载器

public class ClassLoaderTest {
public static void main(String[] args) {
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
// 默认由 AppClassLoader 加载类
System.out.println(classLoader);
// ExtClassLoader
System.out.println(classLoader.getParent());
// Bootstrap ClassLoader,由 JVM 启动
System.out.println(classLoader.getParent().getParent());
}
}

除系统提供的加载器外,还可以自己定义类加载器。(继承 java.lang.ClassLoader 类实现)

二、类加载器工作方式(加载机制)

2.1.委托机制(委派模型 或 父委派模型)

委派模型是描述类加载器之间的层次关系。

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此。

因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(找不到所需的类)时,子加载器才会尝试自己去加载。

在 java.lang.ClassLoader 中的 loadClass() 方法中实现该了过程。

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 {
c = findBootstrapClassOrNull(name); // 父类加载器是启动类加载,委托给启动类加载器加载,启动类加载器没有父类加载器。
}
} catch (ClassNotFoundException e) {
// 如果从非 null 的父类加载器中找不到该类,则抛出 ClassNotFoundException
} if (c == null) {
long t1 = System.nanoTime();
// 如果仍未找到,则调用 findClass 查找该类
c = findClass(name); // 这是定义的类加载器; 记录统计数据
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
// 解析类,属于类加载的 link 阶段
resolveClass(c);
}
return c;
}
} /**
* ClassLoader 的子类建议重写 findClass 方法,而不是 loadClass
*/
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}

自己写的 java.lang.String 类,是否可以替换 JDK 自带的类?

答案是不行的。但这非委托机制解决的,因为委托机制是可以被打破的,完全可以写一个 classLoader 来加载自己写的 java.lang.String 类。

但是你会发现也加载不成功,因为 JVM 的实现中已经保证了 java.* 开头的类必须由 bootstrp 来加载。
 

2.2.可见性机制

子类加载器可以看到父类加载器加载的类,而反之则不行。当 Abc.class 已经被 Application 类加载器加载过了,然后想要使用 Extension 类加载器加载这个类,将会抛出 java.lang.ClassNotFoundException 异常。

2.3.单一性机制

根据委托机制,父加载器加载过的类不能被子加载器加载第二次。虽然重写 loadClass() 的类加载器可以做到不遵守委托机制和单一性机制,但这样做并不可取。

判断类是否“相等”

任意一个类,都由加载它的类加载器和这个类本身一同确立其在 Java 虚拟机中的唯一性,每一个类加载器,都有一个独立的类名称空间。

因此,比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个 Class 文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那么这两个类就必定不相等。

这里的“相等”,包括代表类的 Class 对象的 equals() 方法、isInstance() 方法的返回结果,也包括使用 instanceof 关键字做对象所属关系判定等情况。

关于破坏委派模型

java 引入了线程上下文类加载器(Thread Context ClassLoader),这个类加载器可以通过 Thread 类的 setContextClassLoader 进行设置,默认继承父线程类加载器,也可由父类加载器请求子类加载器完成类加载动作。

https://www.jianshu.com/p/09f73af48a98


https://docs.oracle.com/javase/8/docs/api/java/lang/ClassLoader.html

https://docs.oracle.com/javase/tutorial/ext/basics/load.html

https://blog.csdn.net/lengxiao1993/article/details/86689331

https://github.com/doocs/jvm/blob/master/docs/10-class-loader.md

Java-JVM 类加载机制的更多相关文章

  1. Java JVM类加载机制

    虚拟机的类加载机制是:JVM把描述类的数据从.class文件加载到内存,并对数据进行校验.解析.初始化,最终形成可以被JVM直接使用的Java类型. 加载.连接(验证.准备.解析).初始化.使用.卸载 ...

  2. Java虚拟机(四):JVM类加载机制

    1.什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构 ...

  3. Java虚拟机(五):JVM 类加载机制

    一.JVM 类加载机制 JVM 类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程. 1. 加载: 加载是类加载过程中的第一个阶段,这个阶段会在内存中生成一个代表 ...

  4. Java基础篇(JVM)——类加载机制

    这是Java基础篇(JVM)的第二篇文章,紧接着上一篇字节码详解,这篇我们来详解Java的类加载机制,也就是如何把字节码代表的类信息加载进入内存中. 我们知道,不管是根据类新建对象,还是直接使用类变量 ...

  5. Java虚拟机类加载机制——案例分析

    转载: Java虚拟机类加载机制--案例分析   在<Java虚拟机类加载机制>一文中详细阐述了类加载的过程,并举了几个例子进行了简要分析,在文章的最后留了一个悬念给各位,这里来揭开这个悬 ...

  6. JVM基础系列第7讲:JVM 类加载机制

    当 Java 虚拟机将 Java 源码编译为字节码之后,虚拟机便可以将字节码读取进内存,从而进行解析.运行等整个过程,这个过程我们叫:Java 虚拟机的类加载机制.JVM 虚拟机执行 class 字节 ...

  7. Java 的类加载机制

    Java 的类加载机制 来源 https://www.cnblogs.com/xiaoxi/p/6959615.html 一.什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内 ...

  8. JVM总结(四):JVM类加载机制

    这一节我们来总结一下JVM类加载机制.具体目录如下: 类加载的过程 类加载过程概括 说说引用 详解类加载全过程: 加载 验证 准备 解析 初始化 虚拟机把描述类的数据从Class文件加载到内存,并对数 ...

  9. Java基础-类加载机制与自定义类Java类加载器(ClassLoader)

    Java基础-类加载机制与自定义类Java类加载器(ClassLoader) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 关于类加载器的概念和分类我就不再废话了,因为我在之前的笔 ...

  10. JVM 类加载机制详解

    如下图所示,JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程. 加载 加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这个类的java.lan ...

随机推荐

  1. Go 工作空间 深度解析

    介绍 这篇文档举例证明了一个简单地 Go package 并且介绍了 go tool,标准的方法来 fetch, build,and install Go package and commands. ...

  2. maven 父子工程打包 并且上传linux服务器

    先对父工程进行 mvn clean 再对子工程执行 install wagon:upload-single wagon:sshexec 使用wagon前提: 本地maven 的settings.xml ...

  3. -bash: ls: No such file or directory 错误的原因及解决办法

    ubuntu出现如下错误: { Welcome to Ubuntu 16.04.5 LTS (GNU/Linux 4.15.0-42-generic x86_64) * Documentation: ...

  4. Django学习系列19:完成最简单可用的网站——确保功能之间相互隔离

    前面遗留的问题,首先时功能测试运行结束后的清理:其次是目前我们的待办清单只允许创建一个大家公用的清单. 如何隔离测试,运行功能测试后待办事项一直存在于数据库中,这会影响下一次测试. 运行单元测试时,D ...

  5. MyBatis---join 查询

    在实际业务中,经常能碰到多表关联查询 下面的Demo,讲举例join查询在MyBatis中的实现 User 类: package com.zy.domain; import java.io.Seria ...

  6. HTML5的快捷方式

    ctrl + /  单行注释 ctrl + shift + /   块注释 ctrl + shift + “+”   展开 ctrl + shift + “-”  折叠 ctrl + alt + L  ...

  7. top命令参数详解

    简介 top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器. top显示系统当前的进程和其他状况,是一个动态显示过程,即可以通过用户按 ...

  8. 回调函数c++类中实现

    https://blog.csdn.net/mrailence/article/details/52251201 https://blog.csdn.net/qq_14820081/article/d ...

  9. autoprefixer 处理css3的前缀

    css3书写的时候,有时需要加上前缀,比如“-webkit-*.-moz-*”等等,但可能会写的不完整或者是写错,也很麻烦,那么autoprefixer可以处理这些. autoprefixer是一个后 ...

  10. CodeForces 778B - Bitwise Formula

    题意: 选择一个 m 位的二进制数字,总分为 n 个算式的答案之和.问得到最低分和最高分分别应该取哪个二进制数字 分析: 因为所有数字都是m位的,高位的权重大于低位 ,我们就从高到低考虑 ans 的每 ...