JVM面试题-类加载顺序、双亲委派、类初始化顺序(详解)

类加载器

JVM只会运行二进制文件,类加载器的作用就是将字节码文件加载到JVM中,从而让Java程序能够启动起来。

类加载负责执行类加载,去磁盘进行识别,识别完后加载到内存

类加载器的种类:

从上往下

  • 启动类加载器:用来加载java核心类库,无法被java程序直接引用,加载的是JAVA_HOME/jre/lib;

  • 扩展类加载器:用来加载java的扩展库, java的虚拟机实现会提供一个扩展库目录,加载的是JAVA_HOME/jre/lib/ext;

  • 应用类加载器:它根据java的类路径来加载类,一般来说,java应用的类都是通过它来加载的,加载的是CLASSPATH;

  • 自定义类加载器:平时用的不多,由java语言实现,继承自AppClassLoader;

双亲委派

加载某一个类,先委托上一级的加载器进行加载,如果上级加载器也有上级,则会继续向上委托,如果该类委托上级没有被加载,子加载器尝试加载该类。

好处:

  1. 可以避免某一个类被重复加载,当父类已经加载后则无需重复加载,保证唯一性。

  2. 为了安全,保证类库API不会被修改。

    例如:用户自己写一个String类的话,执行main方法会报错,因为同名的String类已经被加载了,且这个类里面没有main方法,所以报错,这样就保证了不会让别人修改java的核心API库。

能不能自己写一个类,也叫java.lang.String

可以,但是即使你写了这个类,也没有用,这个问题涉及到加载器的委托机制,层层调用,最底下才是我们自定义的类,Java虚拟机会将java.lang.String类的字节码加载到内存中。

为什么只加载系统通过的java.lang.String类而不加载用户自定义的java.lang.String类呢?

因加载某个类时,优先使用父类加载器加载需要使用的类。如果我们自定义了java.lang.String这个类, 加载该自定义的String类,该自定义String类使用的加载器是AppClassLoader,根据优先使用父类加载器原理,一直往上走,最后在启动类加载器中加载了String类。所以,用户自定义的java.lang.String不被加载,也就是不会被使用。

破坏双亲委派

第一破坏:

我们可以重写ClassLoader的loadClass方法,但是双亲委派的逻辑就是存在于这个方法,重写就会破坏

所以JDK1.2之后引入了findClass方法,重写这个方法而不是loadClass

第二次破坏:

各种数据库,JDK提供接口给他们,他们按照接口实现自己的类库,但调用JDK接口时会引起,接口中的类会引起第三方类库的加载,不符合自上而下的加载机制,出现父类加载器请求子类加载器去完成类加载的动作,

SPI spi机制是一种服务发现机制。它通过在 ClassPath 路径下的 META-INF/services 文件夹查找文件,自动加载文件里所定义的类。这一机制为很多框架扩展提供了可能,比如在JDBC中就使用到了SPI机制。

原生的JDBC中 Driver 驱动本身只是一个接口,并没有具体的实现,具体的实现是由不同数据库厂商去实现的。原生的JDBC中的类是放在 rt.jar 包的,是由启动类加载器进行类加载的,在JDBC中的 Driver 类中需要动态加载不同数据库类型的 Driver 类,而 mysql-connector-.jar 中的 Driver 类是用户自己写的代码,那启动类加载器肯定是不能进行加载的,于是乎,这个时候就引入SPI,程序就可以把原本需要由启动类加载器进行加载的类,由应用程序类加载器去进行加载了。

2、Tomcat Tomcat为什么不使用默认的双亲委派模型?

Tomcat 作为一个 web 容器,存在以下使用场景:

1、部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个程序能使用不同版本的依赖

2、应用程序的类库都是独立的,保证相互隔离;

3、部署在同一个 web 容器中相同的类库相同的版本可以共享;

4、容器也有自己依赖的类库,不能与应用程序的类库混淆。基于安全考虑,应该让容器的类库和程序的类库隔离开来;

2.2、Tomcat 如何实现自己的类加载机制? Tomcat 自己实现了自己的类加载器:

CommonLoader:Tomcat最基本的类加载器,加载路径中的 class 可以被Tomcat容器本身以及各个 Webapp 访问;

CatalinaLoader:Tomcat容器私有的类加载器,加载路径中的 class 对于 Webapp 不可见;

SharedClassLoader:各个 Webapp 共享的类加载器,加载路径中的 class 对于所有 Webapp可见,但是对于Tomcat容器不可见;

WebappClassLoader:各个 Webapp 私有的类加载器,加载路径中的 class 只对当前 Webapp可见;

JspClassLoader:每一个JSP文件对应一个Jsp类加载器。

 

从图中的委派关系中可以看出:

CommonClassLoader 能加载的类都可以被 CatalinaClassLoader 和 SharedClassLoader 使用,从而实现了公有类库的共用; CatalinaClassLoader 和 Shared ClassLoader 自己能加载的类则与对方相互隔离; WebAppClassLoader 可以使用 SharedClassLoader 加载到的类,但各个WebAppClassLoader 实例之间相互隔离; JasperLoader 的加载范围仅仅是这个JSP文件所编译出来的那一个 .Class 文件,它出现的目的就是为了被丢弃:当Web容器检测到JSP文件被修改时,会替换掉目前的 JasperLoader 的实例,并通过再建立一个新的 JasperLoader 来实现JSP文件的热插拔功能。

类加载 过程

虚拟机把描述类的数据加载到内存里面,并对数据进行校验、解析和初始化,最终变成可以被虚拟机直接使用的class对象;

加载--验证--准备--解析--初始化--使用--卸载

  1. 加载:通过全类名获取类的二进制流,然后吧二进制数据流解析成静态数据结构 存储 在方法区,并在堆中生成一个便于用户调用的java.lang.Class类型的对象

  2. 验证:1.验证class文件的格式是够正确。2.检查是否存在自己要使用的其他类或者方法。

  3. 准备:为类的静态变量分配内存,并初始化值

  4. 解析:将类,接口、字段和方法符号引用转为直接引用

    方法中调用了其他方法,方法名可以理解为符号引用,而直接引用就是使用指针直接指向方法

    比如:假设一个类A被编译成class文件,并且A引用了B,在编译阶段,A就需要一个字符串去记录B的地址,字符串就叫符号引用,在运行时,A类被加载,但是B未被加载,那么将加载B,此时A的符号引用会被替换成B的实际地址,这时就是直接引用

  5. 初始化:就是对类的静态变量,静态代码块执行初始化操作。具体初始化顺序,看下面。

  6. 使用:当我们调用静态类成员信息,比如静态字段、静态方法的时候就是使用了,还有用 new 关键字创建实例也是使用。

  7. 卸载:当用户程序代码执行完毕后,JVM便开始销毁创建的Class对象。

类初始化 顺序

主要是下面几个要点、规则

  1. 先父再子。如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。

  2. 自上而下。如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。

  3. 懒惰性质。不是所有的类都会在程序启动时立即初始化,而是根据实际需要进行初始化。例如:如果直接用子类调用父类的属性,父类会初始化,子类不会。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值