1、类加载过程

类加载时机

「加载」

将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在内存上创建一个java.lang.Class对象用来封装类在方法区内的数据结构作为这个类的各种数据的访问入口。

「验证」

主要是为了确保class文件中的字节流包含的信息是否符合当前JVM的要求,且不会危害JVM自身安全,比如校验文件格式、是否是cafe baby魔术、字节码验证等等。

「准备」

为类变量分配内存并设置类变量(是被static修饰的变量,变量不是常量,所以不是final的,就是static的)初始值的阶段。这些变量所使用的内存在方法区中进行分配。比如

  1. private static int age = 26;

类变量age会在准备阶段过后为 其分配四个(int四个字节)字节的空间,并且设置初始值为0,而不是26。

若是final的,则在编译期就会设置上最终值。

「解析」

JVM会在此阶段把类的二进制数据中的符号引用替换为直接引用。

「初始化」

初始化阶段是执行类构造器<clinit>()方法的过程,到了初始化阶段,才真正开始执行类定义的Java程序代码(或者说字节码 )。比如准备阶段的那个age初始值是0,到这一步就设置为26。

「使用」

对象都出来了,业务系统直接调用阶段。

「卸载」

用完了,可以被GC回收了。

2、类加载器种类以及加载范围

类加载器种类

「启动类加载器(Bootstrap ClassLoader)」

最顶层类加载器,他的父类加载器是个null,也就是没有父类加载器。负责加载jvm的核心类库,比如java.lang.*等,从系统属性中的sun.boot.class.path所指定的目录中加载类库。他的具体实现由Java虚拟机底层C++代码实现。

「扩展类加载器(Extension ClassLoader)」

父类加载器是Bootstrap ClassLoader。从java.ext.dirs系统属性所指定的目录中加载类库,或者从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库,如果把用户的jar文件放在这个目录下,也会自动由扩展类加载器加载。继承自java.lang.ClassLoader

「应用程序类加载器(Application ClassLoader)」

父类加载器是Extension ClassLoader。从环境变量classpath或者系统属性java.class.path所指定的目录中加载类。继承自java.lang.ClassLoader

「自定义类加载器(User ClassLoader)」

除了上面三个自带的以外,用户还能制定自己的类加载器,但是所有自定义的类加载器都应该继承自java.lang.ClassLoader。比如热部署、tomcat都会用到自定义类加载器。

补充:不同ClassLoader加载的文件路径配置在如下源码里写的:

  1. // sun.misc.Launcher
  2.  
  3. public class Launcher {
  4. // Bootstrap类加载器的加载路径,在static静态代码块里用的
  5. private static String bootClassPath = System.getProperty("sun.boot.class.path");
  6.  
  7. // AppClassLoader 继承 ClassLoader
  8. static class AppClassLoader extends URLClassLoader {
  9. public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
  10. // java.class.path
  11. final String var1 = System.getProperty("java.class.path");
  12. }
  13. }
  14.  
  15. // ExtClassLoader 继承 ClassLoader
  16. static class ExtClassLoader extends URLClassLoader {
  17. public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
  18. // java.ext.dirs
  19. String var0 = System.getProperty("java.ext.dirs");
  20. }
  21. }
  22. }

3、双亲委派是什么

如果一个类加载器收到了类加载的请求,他首先会从自己缓存里查找是否之前加载过这个class,加载过直接返回,没加载过的话他不会自己亲自去加载,他会把这个请求委派给父类加载器去完成,每一层都是如此,类似递归,一直递归到顶层父类。

也就是Bootstrap ClassLoader,只要加载完成就会返回结果,如果顶层父类加载器无法加载此class,则会返回去交给子类加载器去尝试加载,若最底层的子类加载器也没找到,则会抛出ClassNotFoundException

源码在java.lang.ClassLoader#loadClass(java.lang.String, boolean)

双亲委派模型

4、为啥要有双亲委派

防止内存中出现多份同样的字节码,安全。

比如自己重写个java.lang.Object并放到Classpath中,没有双亲委派的话直接自己执行了,那不安全。双亲委派可以保证这个类只能被顶层Bootstrap Classloader类加载器加载,从而确保只有JVM中有且仅有一份正常的java核心类。如果有多个的话,那么就乱套了。比如相同的类instance of可能返回false,因为可能父类不是同一个类加载器加载的Object。

5、为什么需要破坏双亲委派模型

Jdbc

Jdbc为什么要破坏双亲委派模型?

以前的用法是未破坏双亲委派模型的,比如Class.forName("com.mysql.cj.jdbc.Driver");

而在JDBC4.0以后,开始支持使用spi的方式来注册这个Driver,具体做法就是在mysql的jar包中的META-INF/services/java.sql.Driver文件中指明当前使用的Driver是哪个,然后使用的时候就不需要我们手动的去加载驱动了,我们只需要直接获取连接就可以了。Connection con = DriverManager.getConnection(url, username, password );

首先,理解一下为什么JDBC需要破坏双亲委派模式,原因是原生的JDBC中Driver驱动本身只是一个接口,并没有具体的实现,具体的实现是由不同数据库类型去实现的。例如,MySQL的mysql-connector-*.jar中的Driver类具体实现的。

原生的JDBC中的类是放在rt.jar包的,是由Bootstrap加载器进行类加载的,在JDBC中的Driver类中需要动态去加载不同数据库类型的Driver类,而mysql-connector-*.jar中的Driver类是用户自己写的代码,那Bootstrap类加载器肯定是不能进行加载的,既然是自己编写的代码,那就需要由Application类加载器去进行类加载。

这个时候就引入线程上下文件类加载器(Thread Context ClassLoader),通过这个东西程序就可以把原本需要由Bootstrap类加载器进行加载的类由Application类加载器去进行加载了。

Tomcat

Tomcat为什么要破坏双亲委派模型?

因为一个Tomcat可以部署N个web应用,但是每个web应用都有自己的classloader,互不干扰。比如web1里面有com.test.A.class,web2里面也有com.test.A.class,如果没打破双亲委派模型的话,那么web1加载完后,web2在加载的话会冲突。

因为只有一套classloader,却出现了两个重复的类路径,所以tomcat打破了,他是线程级别的,不同web应用是不同的classloader。

  • Java spi 方式,比如jdbc4.0开始就是其中之一。

  • 热部署的场景会破坏,否则实现不了热部署。

6、如何破坏双亲委派模型

重写loadClass方法,别重写findClass方法,因为loadClass是核心入口,将其重写成自定义逻辑即可破坏双亲委派模型。

7、如何自定义一个类加载器

只需要继承java.lang.Classloader类,然后覆盖他的findClass(String name)方法即可,该方法根据参数指定的类名称,返回对应 的Class对象的引用。

8、热部署原理

采取破坏双亲委派模型的手段来实现热部署,默认的loadClass()方法先找缓存,你改了class字节码也不会热加载,所以自定义ClassLoader,去掉找缓存那部分,直接就去加载,也就是每次都重新加载。

9、常见笔试题

问题:输出结果是什么?

答案:编译报错。

原因:因为静态语句块中只能访问定义在静态语句块之前的变量,定义在他之后的 变量在前面的静态语句块中可以赋值,但是不能访问。

  1. /**
  2. * Description: 编译报错
  3. *
  4. * @author TongWei.Chen 2021-01-08 17:37:44
  5. */
  6. public class Test1 {
  7. static {
  8. // 编译没报错
  9. i = 2;
  10. // 编译报错Illegal forward reference
  11. System.out.println(i);
  12. }
  13. private static int i =1;
  14. }

问题:输出结果是什么?

答案 :1、3

原因:因为类加载过程中会先准备类变量(也就是静态变量),准备阶段是赋初始值阶段,也就是test2=null,value1=0,value2=0,然后进入初始化阶段的时候test2=new Test2(),会执行构造器,结果是value1 = 1,value2 = 4,然后执行value1和value2这两句,value1没变化,value2被重新赋值成了3,所以结果1和3。

  1. public class Test2 {
  2. private static Test2 test2 = new Test2();
  3. private static int value1;
  4. private static int value2 = 3;
  5.  
  6. private Test2() {
  7. value1 ++;
  8. value2 ++;
  9. }
  10.  
  11. public static void main(String[] args) {
  12. // 1
  13. System.out.println(test2.value1);
  14. // 3
  15. System.out.println(test2.value2);
  16. }
  17. }

那如果把private static Test2 test2 = new Test2();放到private static int value2 = 3;下面的话结果就是1和4了。

  1. public class Test3 {
  2. private static int value1;
  3. private static int value2 = 3;
  4. private static Test3 test3 = new Test3();
  5.  
  6. private Test3() {
  7. value1 ++;
  8. value2 ++;
  9. }
  10.  
  11. public static void main(String[] args) {
  12. // 1
  13. System.out.println(test3.value1);
  14. // 4
  15. System.out.println(test3.value2);
  16. }
  17. }

END

推荐好文

强大,10k+点赞的 SpringBoot 后台管理系统竟然出了详细教程!

分享一套基于SpringBoot和Vue的企业级中后台开源项目,代码很规范!

能挣钱的,开源 SpringBoot 商城系统,功能超全,超漂亮!

万万没想到,面试中,连 ClassLoader类加载器 也能问出这么多问题…..的更多相关文章

  1. Java中对于ClassLoader类加载器 嵌套了深度技术的价值

    关于Java技术是一种不断兴起的编程语言,对于ClassLoader 是 Java 届最为神秘的技术之一,无数人被它伤透了脑筋,摸不清门道究竟在哪里.本文我带你彻底吃透 ClassLoader,让你甚 ...

  2. 头条编程题 万万没想到之抓捕孔连顺 JavaScript

    [编程题] 万万没想到之抓捕孔连顺 时间限制:1秒 空间限制:131072K 我叫王大锤,是一名特工.我刚刚接到任务:在字节跳动大街进行埋伏,抓捕恐怖分子孔连顺.和我一起行动的还有另外两名特工,我提议 ...

  3. 字节跳动:[编程题]万万没想到之聪明的编辑 Java

    时间限制:1秒 空间限制:32768K 我叫王大锤,是一家出版社的编辑.我负责校对投稿来的英文稿件,这份工作非常烦人,因为每天都要去修正无数的拼写错误.但是,优秀的人总能在平凡的工作中发现真理.我发现 ...

  4. java ClassLoader类加载器

    原文 首先来了解一下字节码和class文件的区别: 我们知道,新建一个java对象的时候,JVM要将这个对象对应的字节码加载到内存中,这个字节码的原始信息存放在classpath(就是我们新建Java ...

  5. ClassLoader类加载器 & Java类加载机制 & 破坏双亲委托机制

    ClassLoader类加载器 Java 中的类加载器大致可以分成两类: 一类是系统提供的: 引导类加载器(Bootstrap classloader):它用来加载 Java 的核心库(如rt.jar ...

  6. Java安全之 ClassLoader类加载器

    Java安全之 ClassLoader类加载器 0x00 前言 前面这里抛出一个问题,Java到底是什么类型的编程语言?是编译型?还是解释型?在这个问题是其实一直都都有疑惑,如果说是解释型语言的话,那 ...

  7. ClassLoader类加载器

    总的来说,当动态加载一个资源时,至少有三种类加载器可供选择: 系统类加载器(也被称为应用类加载器)(system classloader) 当前类加载器(current classloader) 当前 ...

  8. 进阶Java编程(11)ClassLoader类加载器【待完成】

    1,ClassLoader类加载器简介 在Java里面提供一个系统的环境变量:ClassPath,这个属性的作用主要是在JVM进程启动的时候进行类加载路径的定义,在JVM里面可以根据类加载器而后进行指 ...

  9. 万万没想到!ModelArts与AppCube组CP了

    摘要:嘘,华为云内部都不知道的秘密玩法,我悄悄告诉您! 双"魔"合璧庆双节 ↑开局一张图,故事全靠编 华为云的一站式开发平台ModelArts和应用魔方AppCube居然能玩到一起 ...

随机推荐

  1. Jenkins 如何实现 拷贝文件到网络共享目录

    在使用jenkins中,发现拷贝文件时,不能在脚本中直接添加脚本实现. 我实现的一种方法,希望能对您有用. net use y: \\server_name\workspace "passw ...

  2. 第 5篇 Scrum 冲刺博客

    一.站立式会议 1.站立式会议照片 2.昨天已完成的工作 售货员页面功能 3.今天计划完成的工作 添加登录系统账号密码数据库模块 继续对商品销售部分进行编码 职工管理页面 4.工作中遇到的困难 ①页面 ...

  3. JAVA_数据类型介绍与基本数据类型之间的运算规则

    基本数据类型 整型: byte.short.int.long java 的整型常量默认为int型,在java程序中变量通常声明为int型,除非不足以表示较大的数才用long,而在声明long型常量必须 ...

  4. 数位DP复习笔记

    前言 复习笔记第五篇.(由于某些原因(见下),放到了第六篇后面更新)CSP-S RP++. luogu 的难度评级完全不对,所以换了顺序,换了别的题目.有点乱,见谅.要骂就骂洛谷吧,原因在T2处 由于 ...

  5. AcWing 345. 牛站 Cow Relays

    由于我太菜了,不会矩阵乘法,所以给同样不会矩阵乘法同学的福利 首先发现这题点很多边很少,实际上有用的点 \(<= 2 * T\)(因为每条边会触及两个点嘛) 所以我们可以把点的范围缩到 \(2 ...

  6. sql注入之union注入

    联合查询注入利用的前提: 必须要有回显 联合查询过程: 判断是否存在注入点 判断是什么类型注入(字符型or数字型) 判断闭合方式 查询列数个数(order by) 5, 获得数据库名 获得表名 获得字 ...

  7. unity入门—资源导入与场景创建

    前言: 从这一篇章开始,我将会通过游戏实例来讲解如何使用unity制作一个标准的游戏,介绍的内容较多,需要整理的东西也多可能中途会有一两天的咕咕咕,预计想要完成两个游戏,一个射击类一个塔防类,从射击类 ...

  8. C#9.0新特性详解系列之六:增强的模式匹配

    自C#7.0以来,模式匹配就作为C#的一项重要的新特性在不断地演化,这个借鉴于其小弟F#的函数式编程的概念,使得C#的本领越来越多,C#9.0就对模式匹配这一功能做了进一步的增强. 为了更为深入和全面 ...

  9. js下 Day20、综合案例

    一.购物车 效果图: 功能思路分析: 1. 面向对象框架 2. 模拟数据 1.多个店铺数组套对象 2.每个店铺多个商品,数组套对象

  10. 多任务-python实现-协程(2.1.11)

    多任务-python实现-协程(2.1.11) 23/100 发布文章 qq_26624329 @ 目录 1.概念 2.迭代器 1.概念 协程与子例程一样,协程(coroutine)也是一种程序组件. ...