类加载器的关系

类加载器的分类

  • JVM支持两种类加载器,一种为引导类加载器(Bootstrap ClassLoader),另外一种是自定义类加载器(User Defined ClassLoader)
  • 引导类加载器是由C/C++编写的无法访问到
  • Java虚拟机规定:所有派生于抽象类ClassLoader的类加载器都划分为自定义加载器
  • 最常见的类加载器只有三个(如上图所示)

用户自定义的类会被系统类加载器所加载,核心类库的类会被引导类加载器所加载

public class ClassLoaderTest {
public static void main(String[] args) {
//获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2 //获取其上层:扩展类加载器
ClassLoader extClassLoader = systemClassLoader.getParent();
System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@1540e19d //获取其上层:获取不到引导类加载器
ClassLoader bootstrapClassLoader = extClassLoader.getParent();
System.out.println(bootstrapClassLoader);//null //对于用户自定义类来说:默认使用系统类加载器进行加载
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2 //String类使用引导类加载器进行加载的。---> Java的核心类库都是使用引导类加载器进行加载的。
ClassLoader classLoader1 = String.class.getClassLoader();
System.out.println(classLoader1);//null
}
}

系统自带的类加载器介绍

  • 启动类加载器(引导类加载器、Bootstrap ClassLoader)
    • 由c/c++语言实现的,嵌套在jvm内部
    • 用来加载java核心库
    • 并不继承java.lang.ClassLoader,没有父加载器
    • 为扩展类加载器和系统类加载器的父加载器
    • 只能加载java、javax、sun开头的类
  • 扩展类加载器(Extension ClassLoader)
    • java语言编写,sun.misc.Launche包下。
    • 派生于ClassLoader类,父类加载器为Bootstrap ClassLoader
    • 从java.ext.dirs系统属性指定的目录中加载类库或者加载jre/lib/ext子目录下的类库(用户可以在该目录下编写JAR,也会由此加载器所加载)
  • 系统类加载器(System ClassLoader\AppClassLoader)
    • 派生于ClassLoader,父类加载器为Extension ClassLoader
    • 负责加载classpath或者系统属性java.class.path指定路径下的类库
    • java语言编写,sun.misc.Launche包下。
    • 负责加载程序中默认的类,可以通过getSystemClassLoader()方法获取该类的加载器。
  • 用户自定义类加载器(后面详细介绍)
    • 隔离加载类
    • 修改类加载的方式
    • 扩展加载源
    • 防止源码泄漏(可以对字节码文件加密)
    • 继承ClassLoader类方式实现自定义类加载器

关于ClassLoader

  • ClassLoader是一个抽象类,其后的所有类加载器都继承此类

注:这些方法都不是抽象方法。

获取ClassLoader的路径

public class ClassLoaderTest2 {
public static void main(String[] args) {
try {
//1.
ClassLoader classLoader = Class.forName("java.lang.String").getClassLoader();
System.out.println(classLoader);
//2.
ClassLoader classLoader1 = Thread.currentThread().getContextClassLoader();
System.out.println(classLoader1);
//3.
ClassLoader classLoader2 = ClassLoader.getSystemClassLoader().getParent();
System.out.println(classLoader2);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}

双亲委派机制(面试)

  • Java虚拟机对class文件采用的是按需加载的方式,当需要使用到这个类的时候才会对它的class文件加载到内存生成class对象,加载的过程中使用的双亲委派模式,即把请求交给父类处理。

  • 如果一个类加载器收到了类加载的请求,它不会自己加载,而是先把这个请求给自己的父类加载器去执行

  • 如果这个父类加载器还有父类加载器,则会再将请求给自己的父类加载器,依次递归到顶层的启动类加载器

  • 依次进行判断是否能完成委派(加载此类),若能完成委派则该类就由此加载器加载,若无法完成委派,则将委托给子类加载器进行判断是否能完成委派,依次递归到底层加载器,若期间被加载则完成加载阶段不会再递归(注)。

 注:类只能被一个加载器所加载。
  • 双亲委派的优势

  • 避免类的重复加载

  • 保护程序的安全,防止核心API被篡改

  • 例如:

  • 创建一个java.lang.String类,因为有双亲委派的机制,所以会将String类交给引导类加载器来判断是否能被加载。引导加载器判断可以加载此类(是核心类中的String),完成加载,则"恶意"写的String类无法生效,防止String类被恶意篡改。这里也称沙箱安全机制(保护java核心源代码)

package java.lang;
public class String {
//
static{
System.out.println("我是自定义的String类的静态代码块");
}
//错误: 在类 java.lang.String 中找不到 main 方法
public static void main(String[] args) {
System.out.println("hello,String");
}
} //因为加载的是核心类的String,在String中找不到main方法
public class StringTest {

    public static void main(String[] args) {
java.lang.String str = new java.lang.String();//无输出 StringTest test = new StringTest();
System.out.println(test.getClass().getClassLoader());
}
}
package java.lang;

public class ShkStart {

    //错误:java.lang.SecurityException: Prohibited package name: java.lang
public static void main(String[] args) {
System.out.println("hello!");
}
} //因为java.lang包由引导类加载器加载,引导类中并没有此类,为了安全引导类

破坏双亲委派模型:

较大规模的破坏双亲委派模型的有3种:

  • 由于双亲委派模型是在JDK1.2之后才引入的,所以在JDK1.2之前是不符合双亲委派模型的:

  • ClassLoader这类在JDK1.0开始有存在的,在JDK1.2之前,ClassLoader中是通过私有方法loadClassInternal()去调用自己内部的loadClass()。为了满足双亲委派以及向下兼容,在JDK1.2后的ClassLoader类中,又为该类添加了protected的findClass()方法,JDK1.2之后就不推荐通过覆盖重写loadClass()方法了,而是在新添加的findClass()方法中书写自己的类加载逻辑,若loadClass()方法中的父类加载失败则会调用自己的findClass()方法。

  • 由于双亲委派模型的旨意是越核心的类越由高层的加载器所加载(上文提到过的String类),倘若这些核心类要去调用用户的基础类,例如JNDI服务(是对 资源进行集中管理和查找,它需要调用由独立厂商实现并部署在应用程序的ClassPath下的JNDI接口提供者(SPI,Service Provider Interface)的代码,但启动类加载器不可能"认识"这些代码)

  • 为了解决调用问题,设计了一个线程上下文类加载器(Thread Context ClassLoader),这个类加载器可以通过java.lang.Thread类的setContextClassLoaser()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。

  • 有了这个上下文类加载器,就可以去加载所需要的SPI代码,实际上就是从父类加载器去请求子类加载器去完成类的加载驱动,违背了双亲委派的一般性规则。Java中所有涉及SPI的加载动作基本上都采用这种方式,例如JNDI、JDBC、JCE、JAXB和JBI等。

  • 为了满足"热部署"、"动态部署"等功能而导致的。在OSGi(动态模块技术)环境下,类加载器不再是双亲委派模型中的树状结构,而是进一步发展为更加复杂的网状结构,当收到类加载请求时,OSGi将按照顺序进行类搜索

关于类加载器的一些补充

1. JVM中判断一个类是否是同一个类有两个必要条件:
  • 这两个类的全限定名要一致
  • 这两个类被同一个类加载器加载。
2. 对类加载器的引用:
  • JVM必须知道一个类型是由引导类加载器(启动类加载器)加载的还是由用户类加载器加载的。
  • 如果一个类是由用户类加载器所加载的,那么JVM会将这个类加载器的一个引用作为类信息的一部分保存在方法区。
  • 当解析一个类型到另外一个类型的引用的时候,JVM需要保证这两个类型的类加载器是相同的。
3. 类的主动使用和被动使用
  • 主动使用:
  • 创建类的实例
  • 访问某个类或接口的静态变量,或者对该静态变量赋值
  • 调用类的静态方法
  • 反射

    * 初始化一个类的子类

    * JVM启动时被标明为启动类的类
  • JDK 7 开始提供的动态代理:java.invoke.MethodHandle实例的解析结果,REF_getStatic、REF_putStatic、REF_invokeStatic句柄对应的类没有初始化、则初始化

    * 除以上7种情况,其他使用Java类的方式都为被动使用,被动使用不会导致类的初始化。

最后

大家看完有什么不懂的可以在下方留言讨论,也可以关注我私信问我,我看到后都会回答的。也欢迎大家关注我的公众号:前程有光,马上金九银十跳槽面试季,整理了1000多道将近500多页pdf文档的Java面试题资料放在里面,助你圆梦BAT!文章都会在里面更新,整理的资料也会放在里面。谢谢你的观看,觉得文章对你有帮助的话记得关注我点个赞支持一下!

面试BAT必问的JVM,今天我们来说一说它类加载器的底层原理的更多相关文章

  1. 面试必问:JVM类加载机制详细解析

    前言 在Java面试中,简历上有写JVM(Java虚拟机)相关的东西,JVM的类加载机制基本是面试必问的知识点. 类的加载和卸载 JVM是虚拟机的一种,它的指令集语言是字节码,字节码构成的文件是cla ...

  2. 这道面试必问的JVM面试题70%的Java程序员会做错

    前言 聊聊JVM,一个熟悉又陌生的名词,从认识Java的第一天起,我们就会听到这个名字,在参加工作的前一两年,面试的时候还会经常被问到JDK,JRE,JVM这三者的区别. JVM可以说和我们是老朋友了 ...

  3. 面试必问之JVM篇

    前言 只有光头才能变强 JVM在准备面试的时候就有看了,一直没时间写笔记.现在到了一家公司实习,闲的时候就写写,刷刷JVM博客,刷刷电子书. 学习JVM的目的也很简单: 能够知道JVM是什么,为我们干 ...

  4. 面试阿里百分百问的Jvm,别问有没有必要学,真的很有必要朋友

    面试阿里百分百问的Jvm,别问有没有必要学,真的很有必要朋友 前言: JVM 的内存模型和 JVM 的垃圾回收机制一直是 Java 业内从业者绕不开的话题(实际调优.面试)JVM是java中很重要的一 ...

  5. jvm入门及理解(二)——类加载器子系统

    一.类加载子系统的作用 类加载子系统负责从文件系统或者网络中加载Class文件,class文件在文件开头有特定的文件标识: ClassLoader只负责class文件的加载,至于它是否可以运行,则由E ...

  6. 大厂必问的JVM面试题

    本文目录: 讲一下JVM内存结构? 程序计数器 虚拟机栈 本地方法栈 堆 方法区 运行时常量池 直接内存 Java对象的定位方式 说一下堆栈的区别? 什么情况下会发生栈溢出? 类文件结构 什么是类加载 ...

  7. 关于JVM内存模型,GC策略以及类加载器的思考

    JVM内存模型 Sun在2006年将Oracle JDK开源最终形成了Open JDK项目,两者在绝大部分的代码上都保持一致.JVM的内存模型是围绕着原子性(操作有且仅有一个结果).可见性(racin ...

  8. 那些面试官必问的JAVA多线程和并发面试题及回答

    Java多线程面试问题 1. 进程和线程之间有什么不同? 一个进程是一个独立(self contained)的运行环境,它可以被看作一个程序或者一个应用.而线程是在进程中执行的一个任务.Java运行环 ...

  9. 面试说熟练掌握各种MQ?那你先看看这道题,面试官必问!

    写在前面 我们知道,目前市面上的MQ包括Kafka.RabbitMQ.ZeroMQ.RocketMQ等等. 那么他们之间究竟有什么本质区别,分别适用于什么场景呢? 上述抛出的问题,同样在不少公司的Ja ...

随机推荐

  1. 通过IIS部署,将图片或者视频等文件用http协议网址访问

    打开IIS管理器 又键点击添加网站 然后到这个界面 文件夹里有这些图片,随便用的一些图片 然后我这里用的是局域网测试,所以IP就是wifi的IP地址,如果是服务器的话,直接选服务器本身的IP地址就行了 ...

  2. java面试题目之JVM(YW制作仅供参考)

    1.JVM工作原理 2.JVM组成部分及其作用. java虚拟机分为两个子系统和两个组件. 两个子系统分别是类加载器和执行引擎,类加载器负责加载字节码(.class)文件到JVM的内存中,执行引擎负责 ...

  3. 项目实战:流水线图像显示控件(列刷新、1ms一次、缩放、拽拖、拽拖预览、性能优化、支持OpenGL GPU加速)

      需求   流水线图像扫描采集控件(带模拟数据测试)性能需求  1.需至少满足可1ms接收一次列数据,而不丢包(接收后可不必立马显示)  2.图片刷新率可达30HZ:限制需求  1.图片高度最小只能 ...

  4. OpenCascade拓扑对象之:TopoDS_Shape对象及其子对象

    @font-face { font-family: "Times New Roman" } @font-face { font-family: "宋体" } @ ...

  5. 常用命令--windows

    查看端口号是否占用并杀进程 1 netstat -ano | findstr " " 2 tasklist | findstr " " 3 taskkill / ...

  6. JWT原理

    1.COOKIE使用和优缺点 https://www.cnblogs.com/xiaonq/p/11094480.html   1.1 cookie原理: 用户名+密码 cookie是保存在用户浏览器 ...

  7. Python爬虫之线程池

    详情点我跳转 关注公众号"轻松学编程"了解更多. 一.为什么要使用线程池? 对于任务数量不断增加的程序,每有一个任务就生成一个线程,最终会导致线程数量的失控,例如,整站爬虫,假设初 ...

  8. LWJGL3的内存管理

    LWJGL3的内存管理 LWJGL3 (Lightweight Java Game Library 3),是一个支持OpenGL,OpenAl,Opengl ES,Vulkan等的Java绑定库.&l ...

  9. 设计Twitter 时间线

    「design Twitter」是 LeetCode 上第 335 道题目,不仅题目本身很有意思,而且把合并多个有序链表的算法和面向对象设计(OO design)结合起来了,很有实际意义,本文就带大家 ...

  10. 浅析 AC 自动机

    目录 简述 AC 自动机是什么 AC 自动机有什么用 AC 自动机·初探 AC 自动机·原理分析 AC 自动机·代码实现 AC 自动机·更进一步 第一题 第二题 第三题 从 AC 自动机到 fail ...