最近收到粉丝私信,大厂面试常问:“什么是双亲委派机制?类加载过程怎样?优缺点是什么?原理是何?能打破双亲委派机制吗?如何打破?有哪些场景使用?
”
故借此简单总结一下,更多可以点击 【面试笔记】 覆盖全面考题!!
话不多说,开搞。
什么是双亲委派机制?
1、理解概述
双亲委派机制(Parent Delegation Model)是Java虚拟机(JVM)中的一种类加载机制。它是一种层次化的类加载器结构,通过委派给父类加载器来加载类,以保证类的唯一性和安全性。
在Java中,每个类都需要在运行时被加载到内存中才能被使用。类加载器负责将类的字节码加载到内存中,并生成对应的Class对象。双亲委派机制是一种类加载器的工作方式,它通过一种层次化的结构来加载类,保证类的加载是有序、唯一且安全的。
2、类加载过程
类加载过程是将类的字节码加载到内存中,并生成对应的Class对象的过程。类加载过程主要包括以下几个步骤:
- 加载(Loading):类加载的第一个阶段是加载,即将类的字节码文件加载到内存中。加载阶段由类加载器完成,类加载器根据类的全限定名(包括包名和类名)来定位并读取字节码文件。加载阶段的结果是在内存中生成一个代表该类的Class对象。
- 验证(Verification):在验证阶段,对加载的字节码进行验证,确保字节码的正确性和安全性。验证阶段包括以下几个方面的验证:文件格式验证、元数据验证、字节码验证和符号引用验证。
- 准备(Preparation):在准备阶段,为类的静态变量分配内存并设置默认初始值。这些变量包括静态变量和静态常量。
- 解析(Resolution):在解析阶段,将符号引用解析为直接引用。符号引用是一种符号名称,可以是类、字段、方法等的引用。直接引用是直接指向内存中的地址,可以是指向方法区中的方法、字段等的指针。
- 初始化(Initialization):在初始化阶段,对类进行初始化,包括执行静态变量的赋值和静态代码块的执行。初始化阶段是类加载过程的最后一个阶段,只有在初始化阶段完成后,类才能被正常使用。
需要注意的是,类加载过程是按需加载的,即在首次使用类时才会进行加载。而且类加载过程是线程安全的,即同一个类在多线程环境下只会被加载一次。
另外,类加载过程可以由自定义的类加载器来扩展或修改,默认的类加载器是应用程序类加载器(Application ClassLoader),它负责加载应用程序的类。自定义类加载器可以实现一些特定的需求,如加载加密的字节码文件、从网络或其他非标准位置加载类等。
反映在双亲委派机制上:
具体来说,当一个类加载器收到加载类的请求时,它会先检查自己是否已经加载过这个类。如果已经加载过,则直接返回已加载的类。如果没有加载过,则将加载请求委派给它的父类加载器。父类加载器会按照同样的方式继续检查是否已经加载过这个类,直到达到顶层的启动类加载器。如果所有的父类加载器都无法加载这个类,则由当前类加载器自己去加载。如果加载成功,则将生成的Class对象返回给请求者。
3、类加载器的层次结构
类加载器的层次结构是指类加载器之间的父子关系,它们按照一定的顺序组成了一个层次化的结构。在Java中,双亲委派机制的类加载器结构一般包括三个层次:
- 启动类加载器(Bootstrap ClassLoader):也称为根类加载器,它是虚拟机的一部分,通常由本地代码实现,不是Java类。它负责加载Java的核心类库,如java.lang包中的类。启动类加载器是所有其他类加载器的父加载器,它没有父加载器。
- 扩展类加载器(Extension ClassLoader):它是由Java编写的类,是由启动类加载器加载的。扩展类加载器负责加载Java的扩展类库,如javax包中的类。它的父加载器是启动类加载器。
- 应用程序类加载器(Application ClassLoader):也称为系统类加载器,它负责加载应用程序的类,即开发者自己编写的类。应用程序类加载器是由扩展类加载器加载的。它的父加载器是扩展类加载器。
除了这三个主要的类加载器,还有一些其他的类加载器,如自定义的类加载器。自定义的类加载器可以继承自应用程序类加载器或其他类加载器,形成更复杂的层次结构。
类加载器的层次结构是通过双亲委派机制来实现的。当一个类加载器收到加载类的请求时,它会先向上委派给父类加载器,由父类加载器尝试加载。
父类加载器会按照同样的方式继续向上委派,直到达到顶层的启动类加载器。如果所有的父类加载器都无法加载这个类,则由当前类加载器自己去加载。
这样的层次结构保证了类加载的一致性和安全性,避免了类的重复加载和恶意代码的替换。
双亲委派机制的作用是什么?
双亲委派机制的作用是保证Java类的加载的一致性和安全性;具体来说,双亲委派机制的作用有以下几个方面:
- 避免重复加载:当一个类加载器收到加载类的请求时,它会先向上委派给父类加载器,由父类加载器尝试加载。父类加载器会按照同样的方式继续向上委派,直到达到顶层的启动类加载器。如果所有的父类加载器都无法加载这个类,则由当前类加载器自己去加载。这样的层次结构保证了类的加载只会发生一次,避免了重复加载。
- 类的隔离性:每个类加载器都有自己的命名空间,它只能加载自己命名空间下的类。当一个类加载器尝试加载一个类时,它会先检查自己的命名空间中是否已经加载了这个类。如果已经加载,则直接返回已加载的类;如果没有加载,则委派给父类加载器加载。这样的隔离性保证了不同类加载器加载的类之间互不影响,避免了类的冲突和版本不一致的问题。
- 安全性:通过双亲委派机制,Java类的加载可以由更高层次的类加载器来完成,这些类加载器通常是由Java官方或其他可信任的实体提供的。这样可以确保核心类库和扩展类库的安全性,避免了恶意代码的替换和执行。
总的来说,双亲委派机制通过层次化的类加载器结构,保证了Java类的加载的一致性和安全性,避免了重复加载和类的冲突,同时也提供了一种安全的加载机制,防止恶意代码的执行。
然而,双亲委派机制也有一些缺点:
- 限制了类加载的灵活性:双亲委派机制要求类加载器按照一定的顺序去加载类,这限制了类加载的灵活性。有时候,我们可能需要自定义的类加载器来加载特定的类,但由于双亲委派机制的存在,这些类可能会被父类加载器加载,无法实现自定义加载的需求。
- 难以实现类的动态更新:由于双亲委派机制的存在,当一个类被加载后,它的类定义就不能再被修改。这意味着如果我们想要在运行时动态更新一个类的定义,就需要重新加载整个类及其依赖的类。这对于一些需要频繁更新的应用场景来说,可能会带来一些困扰。
总的来说,双亲委派机制在保证类加载的一致性和安全性方面具有明显的优势,但也存在一定的限制和缺点。在实际应用中,需要根据具体的需求来权衡使用双亲委派机制的利与弊。
双亲委派机制的工作原理是什么?
双亲委派机制的工作原理可以简单概括为以下几个步骤:
- 当一个类加载器收到加载类的请求时,它会先检查自己的命名空间中是否已经加载了这个类。如果已经加载,则直接返回已加载的类;如果没有加载,则继续下一步。
- 类加载器会将加载请求委派给父类加载器。父类加载器会按照同样的方式继续向上委派,直到达到顶层的启动类加载器。
- 如果所有的父类加载器都无法加载这个类,则由当前类加载器自己去加载。当前类加载器会根据自己的加载策略,从指定的路径或资源中加载类的字节码,并将其转换为可执行的类。
- 加载完成后,将加载的类及其相关信息存放在当前类加载器的命名空间中,以便后续的类加载请求可以直接使用。
通过这样的层次结构和委派机制,双亲委派机制保证了类的加载只会发生一次,避免了重复加载;同时,也保证了类的隔离性,不同类加载器加载的类之间互不影响;还能提供一种安全的加载机制,防止恶意代码的执行。
需要注意的是,双亲委派机制并不是强制性的,可以根据具体的需求进行调整或扩展。在Java中,可以通过自定义类加载器来改变类加载的行为,实现一些特定的需求。
这样可以使得:
- 避免类的重复加载:通过委派给父类加载器,可以避免同一个类被多个类加载器加载,节省了内存空间。
- 提高类加载的效率:父类加载器已经加载过的类可以直接返回,无需再次加载,提高了类加载的效率。
- 提高Java程序的安全性:防止恶意代码替换核心类库,提高了Java程序的安全性。
然而,有时候我们需要打破双亲委派机制,自定义类加载器来实现特定的需求。这需要谨慎操作,因为打破双亲委派机制可能导致类加载的混乱和安全性问题。
打破双亲委派机制?
为什么要打破双亲委派机制吗?
打破双亲委派机制的主要原因是为了满足一些特定的需求和场景,例如:
- 实现类的热部署:在某些应用场景下,需要在运行时动态加载和替换类,以实现热部署的功能。而双亲委派机制会导致类的加载只发生一次,无法实现类的热替换。通过打破双亲委派机制,可以自定义类加载器,在需要时重新加载和替换类。
- 加载非标准的类文件:有些特殊的类文件,如动态生成的字节码、非标准的类文件格式等,无法通过标准的类加载器加载。通过打破双亲委派机制,可以自定义类加载器,实现对这些非标准类文件的加载和解析。
- 实现类加载的动态控制:有些应用需要对类的加载进行特殊的控制,例如对特定的类进行加密、解密或验证等操作。通过打破双亲委派机制,可以自定义类加载器,在加载类时进行特殊的处理。
需要注意的是,打破双亲委派机制可能会引入一些潜在的风险和问题,如类的冲突、不一致性等。因此,在打破双亲委派机制时,需要谨慎考虑,并确保自定义的类加载器能够正确处理类的加载和依赖关系。
怎样打破双亲委派机制?
在Java中,有以下几种方法可以打破双亲委派机制:
- 自定义类加载器:通过自定义ClassLoader的子类,重写findClass()方法,实现自定义的类加载逻辑。在自定义类加载器中,可以选择不委派给父类加载器,而是自己去加载类。
public class CustomClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 自定义类加载逻辑,例如从特定路径加载类文件
byte[] classBytes = loadClassBytes(name);
return defineClass(name, classBytes, 0, classBytes.length);
}
private byte[] loadClassBytes(String name) {
// 从特定路径加载类文件,并返回字节码
// ...
}
}
- 线程上下文类加载器:通过Thread类的setContextClassLoader()方法,可以设置线程的上下文类加载器。在某些框架或库中,会使用线程上下文类加载器来加载特定的类,从而打破双亲委派机制。
- OSGi框架:OSGi(Open Service Gateway Initiative)是一种动态模块化的Java平台,它提供了一套机制来管理和加载模块。在OSGi中,每个模块都有自己的类加载器,可以独立加载和管理类,从而打破双亲委派机制。
- Java SPI机制:Java SPI(Service Provider Interface)是一种标准的服务发现机制,在SPI中,服务的实现类通过在META-INF/services目录下的配置文件中声明,而不是通过类路径来查找。通过SPI机制,可以实现在不同的类加载器中加载不同的服务实现类,从而打破双亲委派机制。
需要注意的是,打破双亲委派机制可能会引入一些潜在的风险和问题,如类的冲突、不一致性等。在使用这些方法打破双亲委派机制时,需要谨慎考虑,并确保能够正确处理类的加载和依赖关系。
除了上述提到的方法,还有一些其他的方法可以打破双亲委派机制:
- 使用Java Instrumentation API:Java Instrumentation API允许在类加载过程中修改字节码,从而可以在类加载时修改类的加载行为,包括打破双亲委派机制。通过Instrumentation API,可以在类加载前修改类的字节码,使其加载时使用自定义的类加载器。
- 使用Java动态代理:Java动态代理机制可以在运行时生成代理类,并在代理类中实现特定的逻辑。通过使用动态代理,可以在类加载时动态生成代理类,并在代理类中实现自定义的类加载逻辑,从而打破双亲委派机制。
- 使用字节码操作库:可以使用字节码操作库,如ASM、Javassist等,来直接操作字节码,从而修改类的加载行为。通过这些库,可以在类加载时修改字节码,使其加载时使用自定义的类加载器。
在某些框架或场景中,为了满足特定的需求,可能会打破双亲委派机制。以下是一些常见的框架或场景:
- JavaEE容器:JavaEE容器(如Tomcat、WebLogic、WebSphere等)通常会使用自定义的类加载器来加载应用程序的类,以实现应用程序的隔离和独立性。这些容器会打破双亲委派机制,使用自定义的类加载器来加载应用程序的类。
- OSGi框架:OSGi(Open Service Gateway Initiative)是一种动态模块化的Java平台,它提供了一套机制来管理和加载模块。在OSGi中,每个模块都有自己的类加载器,可以独立加载和管理类,从而打破双亲委派机制。
- Java SPI机制:Java SPI(Service Provider Interface)是一种标准的服务发现机制,在SPI中,服务的实现类通过在META-INF/services目录下的配置文件中声明,而不是通过类路径来查找。通过SPI机制,可以实现在不同的类加载器中加载不同的服务实现类,从而打破双亲委派机制。
- 动态代理框架:一些动态代理框架,如CGLIB、Byte Buddy等,可以在运行时生成代理类,并在代理类中实现特定的逻辑。这些框架通常会使用自定义的类加载器来加载生成的代理类,从而打破双亲委派机制
总结
双亲委派机制是Java类加载器的一种工作机制,它的核心思想是在类加载的过程中,优先将加载请求委派给父类加载器,只有在父类加载器无法加载时,才由子类加载器尝试加载。
双亲委派机制的主要特点和优势包括:
- 避免类的重复加载:当一个类被加载后,它会被父类加载器缓存起来,避免了重复加载同一个类的问题,提高了类加载的效率。
- 类的隔离和安全性:通过双亲委派机制,不同的类加载器加载的类具有不同的命名空间,相同类名的类可以被不同的类加载器加载,实现了类的隔离和安全性。
- 保护核心类库的完整性:核心类库由启动类加载器加载,避免了用户自定义的类替换核心类库的情况,保护了核心类库的完整性。
总结起来:
- 双亲委派机制通过层级结构的类加载器组织,实现了类的共享、隔离和安全性。
- 它是Java类加载器的一种重要机制,为Java应用程序提供了良好的类加载环境。
- 然而,在某些特定的场景下,为了满足特定的需求,可能需要打破双亲委派机制,使用自定义的类加载器来加载类。
- 在使用自定义类加载器时,需要仔细评估和测试,确保能够正确处理类的加载和依赖关系。
本文已收录到我的面试小站 www.java2top.cn,【Java原创八股文面试网-助力你的大厂offer】
汇总收录各互联网大厂实时高频面经攻略(含详解)& 热点+原创项目🔥
其中包含的内容有:Redis、JVM、并发、并发、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、设计模式、消息队列等模块。