凡科建站好用吗php移动网站开发
凡科建站好用吗,php移动网站开发,网站网站制作网站,怎么自己做彩票网站#x1f343; 予枫#xff1a;个人主页#x1f4da; 个人专栏: 《Java 从入门到起飞》《读研码农的干货日常》《Java 面试刷题指南》#x1f4bb; Debug 这个世界#xff0c;Return 更好的自己#xff01; 引言 作为Java程序员#xff0c;类加载机制是绕不开的核心知识点… 予枫个人主页 个人专栏: 《Java 从入门到起飞》《读研码农的干货日常》《Java 面试刷题指南》 Debug 这个世界Return 更好的自己引言作为Java程序员类加载机制是绕不开的核心知识点而双亲委派模型更是类加载的“黄金法则”。无论是面试中高频被问的“双亲委派的核心作用是什么”还是工作中可能遇到的“类加载冲突”都需要我们吃透这个模型。今天就从核心作用、打破场景、实战案例三个维度把双亲委派模型讲透再补充面试官常追问的高频问题帮你面试、工作双丰收文章目录引言一、双亲委派模型核心解析必吃透1.1 什么是双亲委派模型1.2 核心类加载器层级自上而下1.3 双亲委派模型的3个核心作用面试高频1防止类重复加载保证类的唯一性2保护核心类防止核心API被篡改3实现类加载的层级化提升加载效率二、为什么要打破双亲委派模型实战场景2.1 场景1核心类需要加载用户自定义的实现类2.2 场景2热部署动态加载类2.3 场景3自定义类加载规则如加密类加载三、打破双亲委派的3种实战方法附案例3.1 方法1重写loadClass()方法完全打破实战案例自定义类加载器打破双亲委派说明3.2 方法2重写loadClass()方法部分打破实战案例部分打破双亲委派只加载指定包下的类说明3.3 方法3使用线程上下文类加载器Thread Context ClassLoader核心原理实战案例JDBC中线程上下文类加载器的应用简化版说明四、面试官追问环节实战八股必背追问1双亲委派模型的核心优势和弊端分别是什么追问2JDK1.8中ClassLoader的loadClass()方法默认实现逻辑是什么源码级追问3Tomcat是如何打破双亲委派模型的高频面试追问4打破双亲委派后如何避免类加载冲突五、总结一、双亲委派模型核心解析必吃透1.1 什么是双亲委派模型双亲委派模型是Java类加载器的核心加载机制核心逻辑是当一个类加载器需要加载某个类时它不会先自己去加载而是先委托给父类加载器去加载只有父类加载器无法加载找不到该类时子类加载器才会尝试自己加载。简单来说就是“先找爸爸爸爸不行再自己上”这里的“父子”并非继承关系而是组合关系通过parent属性关联。1.2 核心类加载器层级自上而下Java默认有4个核心类加载器层级关系如下从父到子启动类加载器Bootstrap ClassLoader最顶层由C实现负责加载JDK核心类如rt.jar中的类无法通过Java代码直接获取。扩展类加载器Extension ClassLoader加载JDK扩展目录jre/lib/ext下的类父加载器是启动类加载器。应用程序类加载器Application ClassLoader也叫系统类加载器加载当前应用classpath下的类父加载器是扩展类加载器是我们日常开发中最常用的类加载器。自定义类加载器Custom ClassLoader开发者自定义的类加载器继承ClassLoader可根据需求定制加载规则父加载器默认是应用程序类加载器。用mermaid图直观展示层级和委派流程委托加载委托加载委托加载无法加载无法加载无法加载自己加载自定义类加载器应用程序类加载器扩展类加载器启动类加载器加载成功1.3 双亲委派模型的3个核心作用面试高频这是面试中最常问的点记住这3点轻松应对基础提问1防止类重复加载保证类的唯一性如果没有双亲委派多个类加载器可能会加载同一个类比如自定义类加载器和应用程序类加载器都加载java.lang.String导致内存中出现多个相同的类对象破坏类的唯一性进而引发程序异常。通过双亲委派所有类加载请求都会先委托给顶层的启动类加载器只有顶层加载不了才会向下传递确保同一个类只会被一个类加载器加载一次。2保护核心类防止核心API被篡改Java核心类如java.lang.String、java.lang.Object由启动类加载器加载开发者自定义的类加载器无法加载这些核心类。举个例子如果没有双亲委派我们可以自定义一个java.lang.String类重写其中的方法这样就会篡改核心API的功能导致程序运行混乱。而双亲委派机制下自定义类加载器会先委托父类加载器加载java.lang.String最终由启动类加载器加载核心类避免了核心API被恶意篡改。3实现类加载的层级化提升加载效率双亲委派模型明确了类加载器的层级关系不同层级的类加载器负责加载不同范围的类分工明确。核心类由启动类加载器加载一次加载全局复用应用类由应用程序类加载器加载避免了重复加载提升了类加载的效率。 小贴士记不住核心作用可以简化为“去重、护核、提效”面试时展开说轻松拿分记得点赞收藏后续复习不迷路~二、为什么要打破双亲委派模型实战场景双亲委派模型是Java的默认机制但并非铁律在一些特殊场景下我们必须打破它否则无法实现需求。常见的3个场景2.1 场景1核心类需要加载用户自定义的实现类最典型的就是JDBC场景JDBC的核心接口如java.sql.Driver由启动类加载器加载属于JDK核心类但JDBC的实现类如MySQL的com.mysql.cj.jdbc.Driver却在用户的classpath下由应用程序类加载器加载。按照双亲委派模型启动类加载器加载核心接口后无法加载用户自定义的实现类因为启动类加载器不加载classpath下的类此时就需要打破双亲委派让核心类能够加载用户实现类。2.2 场景2热部署动态加载类热部署是指在程序运行过程中动态替换已加载的类无需重启程序如Tomcat的热部署功能。双亲委派模型下类加载器一旦加载某个类就会缓存该类无法重新加载。要实现热部署就需要自定义类加载器打破双亲委派每次加载类时都重新加载而不是委托给父类加载器。2.3 场景3自定义类加载规则如加密类加载在一些安全场景下我们会对类文件进行加密防止被反编译。此时需要自定义类加载器先对加密的类文件进行解密再加载该类。按照双亲委派模型自定义类加载器会先委托父类加载器加载但父类加载器无法解密加密的类文件无法加载此时就需要打破双亲委派让自定义类加载器直接加载解密后的类。三、打破双亲委派的3种实战方法附案例打破双亲委派的核心思路重写类加载器的loadClass()方法跳过“委托父类加载”的逻辑直接由当前类加载器加载类。Java中所有类加载器都继承自ClassLoader类ClassLoader的默认loadClass()方法实现了双亲委派逻辑我们只需重写该方法即可打破双亲委派。3.1 方法1重写loadClass()方法完全打破重写loadClass()方法直接跳过委托父类加载的步骤所有类都由当前自定义类加载器加载适合需要完全自定义加载规则的场景。实战案例自定义类加载器打破双亲委派importjava.io.*;/** * 自定义类加载器完全打破双亲委派 * 予枫 | CSDN */publicclassCustomClassLoaderextendsClassLoader{// 类文件所在路径可自定义privateStringclassPath;publicCustomClassLoader(StringclassPath){this.classPathclassPath;}// 重写findClass()方法实现类加载的核心逻辑读取类文件、转成字节数组OverrideprotectedClass?findClass(Stringname)throwsClassNotFoundException{try{// 1. 将类名转为文件路径如com.yufeng.Test - com/yufeng/Test.classStringclassNamename.replace(.,/).class;// 2. 读取类文件字节byte[]classBytesloadClassBytes(className);// 3. 将字节数组转为Class对象核心方法returndefineClass(name,classBytes,0,classBytes.length);}catch(IOExceptione){thrownewClassNotFoundException(类加载失败name,e);}}// 读取类文件字节数组privatebyte[]loadClassBytes(StringclassName)throwsIOException{FilefilenewFile(classPathFile.separatorclassName);try(InputStreamisnewFileInputStream(file);ByteArrayOutputStreambosnewByteArrayOutputStream()){byte[]buffernewbyte[1024];intlen;while((lenis.read(buffer))!-1){bos.write(buffer,0,len);}returnbos.toByteArray();}}// 重写loadClass()方法打破双亲委派OverrideprotectedClass?loadClass(Stringname,booleanresolve)throwsClassNotFoundException{synchronized(getClassLoadingLock(name)){// 1. 检查当前类加载器是否已经加载过该类Class?cfindLoadedClass(name);if(cnull){// 2. 跳过委托父类加载直接调用findClass()加载打破双亲委派的核心cfindClass(name);}// 3. 解析类resolve为true时if(resolve){resolveClass(c);}returnc;}}// 测试publicstaticvoidmain(String[]args)throwsException{// 自定义类加载器加载指定路径下的类CustomClassLoaderclassLoadernewCustomClassLoader(D:/test/classes);// 加载com.yufeng.Test类Class?clazzclassLoader.loadClass(com.yufeng.Test);// 实例化并调用方法Objectobjclazz.newInstance();clazz.getMethod(sayHello).invoke(obj);}}说明重写loadClass()方法后跳过了“委托父类加载”的逻辑直接调用findClass()方法加载类完全打破了双亲委派。注意这种方式会导致核心类如java.lang.String也被自定义类加载器加载可能引发安全问题一般用于特殊场景如加密类加载。3.2 方法2重写loadClass()方法部分打破重写loadClass()方法对特定类如用户自定义类跳过委托父类加载对核心类如java.lang包下的类仍遵循双亲委派避免安全问题最常用的方式。实战案例部分打破双亲委派只加载指定包下的类// 承接上面的CustomClassLoader修改loadClass()方法OverrideprotectedClass?loadClass(Stringname,booleanresolve)throwsClassNotFoundException{synchronized(getClassLoadingLock(name)){Class?cfindLoadedClass(name);if(cnull){// 核心逻辑对com.yufeng包下的类直接自己加载其他类如核心类委托父类加载if(name.startsWith(com.yufeng)){cfindClass(name);// 自己加载}else{// 委托父类加载遵循双亲委派csuper.loadClass(name,resolve);}}if(resolve){resolveClass(c);}returnc;}}说明这种方式只对特定包下的类打破双亲委派核心类仍由父类加载器加载既满足了自定义加载需求又避免了安全问题是实际开发中最常用的方式。3.3 方法3使用线程上下文类加载器Thread Context ClassLoader线程上下文类加载器是Java提供的一种灵活的类加载方式它可以绕过双亲委派模型让父类加载器能够加载子类加载器加载的类典型场景JDBC。核心原理线程上下文类加载器默认是应用程序类加载器。父类加载器如启动类加载器可以通过Thread.currentThread().getContextClassLoader()获取线程上下文类加载器子类加载器进而加载子类加载器能加载的类。实战案例JDBC中线程上下文类加载器的应用简化版// JDBC核心加载逻辑简化publicclassJDBCLoader{publicstaticvoidloadDriver()throwsClassNotFoundException,IllegalAccessException,InstantiationException{// 1. 获取线程上下文类加载器默认是应用程序类加载器ClassLoadercontextClassLoaderThread.currentThread().getContextClassLoader();// 2. 加载MySQL的Driver实现类com.mysql.cj.jdbc.Driver// 该类在classpath下应用程序类加载器可以加载而启动类加载器无法加载Class?driverClasscontextClassLoader.loadClass(com.mysql.cj.jdbc.Driver);// 3. 实例化Driver类driverClass.newInstance();}publicstaticvoidmain(String[]args)throwsException{loadDriver();System.out.println(JDBC驱动加载成功);}}说明JDBC的核心接口java.sql.Driver由启动类加载器加载但启动类加载器无法加载classpath下的Driver实现类。通过线程上下文类加载器应用程序类加载器可以让启动类加载器间接加载Driver实现类从而打破双亲委派的限制这是Java官方推荐的一种打破方式。✅ 实战小贴士实际开发中优先使用“部分打破”或“线程上下文类加载器”的方式避免完全打破双亲委派带来的安全风险。记得收藏这3种方法后续开发直接复用四、面试官追问环节实战八股必背吃透这4个追问面试时面对双亲委派相关问题直接碾压面试官追问1双亲委派模型的核心优势和弊端分别是什么优势① 防止类重复加载保证类唯一性② 保护核心API防止篡改③ 层级化加载提升效率。弊端① 父类加载器无法访问子类加载器加载的类如启动类加载器无法加载classpath下的类② 无法实现热部署类加载后无法重新加载③ 灵活性不足无法满足自定义加载需求。追问2JDK1.8中ClassLoader的loadClass()方法默认实现逻辑是什么源码级默认逻辑就是双亲委派的核心逻辑源码简化如下protectedClass?loadClass(Stringname,booleanresolve)throwsClassNotFoundException{synchronized(getClassLoadingLock(name)){// 1. 检查当前类加载器是否已加载该类Class?cfindLoadedClass(name);if(cnull){longt0System.nanoTime();try{// 2. 有父类加载器委托父类加载if(parent!null){cparent.loadClass(name,false);}else{// 3. 没有父类加载器启动类加载器调用启动类加载器加载cfindBootstrapClassOrNull(name);}}catch(ClassNotFoundExceptione){// 父类加载器无法加载抛出异常}// 4. 父类加载器无法加载调用自己的findClass()方法加载if(cnull){longt1System.nanoTime();cfindClass(name);}}if(resolve){resolveClass(c);}returnc;}}追问3Tomcat是如何打破双亲委派模型的高频面试Tomcat的类加载机制打破了双亲委派核心原因是Tomcat需要部署多个Web应用每个Web应用有自己的classpath需要独立加载自己的类避免多个Web应用之间的类冲突。Tomcat的类加载器层级从父到子启动类加载器Bootstrap扩展类加载器Extension系统类加载器SystemCommon类加载器加载Tomcat核心类所有Web应用共享WebApp类加载器每个Web应用一个加载当前Web应用的类不委托父类加载打破双亲委派Jsp类加载器每个JSP一个加载JSP相关类核心逻辑WebApp类加载器重写了loadClass()方法对当前Web应用的类直接自己加载不委托给父类加载器从而实现多个Web应用的类隔离。追问4打破双亲委派后如何避免类加载冲突主要有3种方式类隔离给不同的类加载器分配不同的加载范围如Tomcat的WebApp类加载器每个Web应用一个避免不同应用的类冲突。指定加载范围重写loadClass()方法时只对特定包下的类打破双亲委派核心类仍遵循双亲委派如方法2的实战案例。统一类加载器对于需要共享的类由统一的类加载器加载如Tomcat的Common类加载器避免重复加载。五、总结本文从核心解析、打破场景、实战方法、面试追问四个维度把双亲委派模型讲透了。核心要点总结如下双亲委派模型的核心先委托父类加载父类加载不了再自己加载核心作用是“去重、护核、提效”。打破双亲委派的场景JDBC、热部署、自定义加密加载。3种实战方法完全重写loadClass()、部分重写loadClass()、线程上下文类加载器最常用。面试追问重点掌握优势弊端、源码逻辑、Tomcat实现、冲突解决轻松应对面试。最后如果你觉得本文对你有帮助记得点赞、收藏、关注我予枫后续会分享更多Java核心知识点和面试干货一起进阶Java工程师如有疑问欢迎在评论区留言讨论~