《深入理解java虚拟机》一书中谈到破坏类加载器的双亲委派机制时,谈到使用线程上下文类加载器可以破坏该机制,使程序逆向使用类加载器。
那什么时候需要破坏双亲委派机制呢?其中一种情况是java提供的服务提供者接口(Service Provider Interface,SPI)。这些SPI的借口由java核心库提供,加载它们的只能是BootstrapClassLoader。这些接口的实现多由第三方提供,实现的jar包一般放置在classpath中由AppClassLoader加载。这些都不是问题,问题是核心库中的类有可能需要调用到SPI实现类的方法,java中方法体内的局域变量的加载是由方法调用者的加载类来完成,意思是核心库中的类方法中调用第三方实现的类方法时是由BootstrapClassLoader来加载这个第三方实现类的,很明显BootstrapClassLoader是加载不到classpath的类,而双亲委派机制只能向上委派不能向下委派给AppClassLoader,因此这时候就需要破坏这个委派机制了。
网上想找个相关的例子,实在找不到好的。。(如果有合适的一定请告诉我),只能看到一个jdbc的例子,但是我个人认为jdbc的SPI并没有破坏双亲委派机制,该文章地址http://blog.csdn.net/yangcheng33/article/details/52631940。
现在我来分析一下该例子,并谈谈我自己对使用上下文类加载器破坏双亲委派机制的理解
该文以mysql为例
//初始化驱动类 Class.forName("com.mysql.jdbc.Driver").getInstance(); //获得数据库连接 Connection conn = java.sql.DriverManager.getConnection(url,"name","password");
com.mysql.jdbc.Driver是java.sql.Driver接口的实现类。上段代码第一句就是初始化com.mysql.jdbc.Driver类,该实现类中包含静态代码块:
static { try { java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } }
首先Class.forName段的代码是我们自己写的不出意外由AppClassLoader加载,则new Driver()这里的com.mysql.jdbc.Driver也是由AppClassLoader加载,registerDriver方法就是将参数存入DriverManager的CopyOnWriteArrayList类的类参数中。
getConnection方法的内容大致为
private static Connection getConnection( String url, java.util.Properties info, Class<?> caller) throws SQLException { /**传入的caller由Reflection.getCallerClass()得到,该方法 * 可获取到调用本方法的Class类,这儿调用者是java.sql.DriverManager(位于/lib/rt.jar中), * 也就是说caller.getClassLoader()本应得到Bootstrap启动类加载器 * 但是在上一篇文章中讲到过启动类加载器无法被程序获取,所以只会得到null */ ClassLoader callerCL = caller != null ? caller.getClassLoader() : null; synchronized(DriverManager.class) { // callerCL得实际类型变为线程上下文类加载器实例 if (callerCL == null) { callerCL = Thread.currentThread().getContextClassLoader(); } } if(url == null) { throw new SQLException("The url cannot be null", "08001"); } SQLException reason = null; for(DriverInfo aDriver : registeredDrivers) { // 查看源码可以看出isDriverAllowed的作用是辨别aDriver.driver是否由callerCL来加载 if(isDriverAllowed(aDriver.driver, callerCL)) { try { println(" trying " + aDriver.driver.getClass().getName()); // 调用com.mysql.jdbc.Driver.connect方法获取连接 Connection con = aDriver.driver.connect(url, info); if (con != null) { // Success! return (con); } //后面代码不用看了 } catch (SQLException ex) { if (reason == null) { reason = ex; } } } else { println(" skipping: " + aDriver.getClass().getName()); } } throw new SQLException("No suitable driver found for "+ url, "08001"); }
可以看出这里实现所谓的核心类调用第三方实现类的方法,不过是将com.msyql.jdbc.Driver的实例存入核心类库中的DriverManager,而DriverManager.getConnection方法只是将该实例拿出来用而已,并没有重新初始化一个实例。这里的线程上下文类加载器只是起一个验证的作用,而不是该问作者所谓的“完全破坏了双亲委派模式”
接下来说说我对线程上下文类加载器破坏双亲委派的实现的一些猜想(个人猜测,没找到具体案例解释,书中也没详细讲)。
我想关键在于Class.forName(className,classLoader)这个方法,核心类库中的类想调用classpath中的类可以通过该方法指定类的加载器,这里就可以通过上下文加载器获得AppClassLoader来加载该类了。其实也可以通过ClassLoader.getSystemClassLoader来获取AppClassLoader,只是我们能修改上下文类加载器不能修改系统类加载器。
相关推荐
线程查看器的另一个亮点是线程上下文切换的可视化。上下文切换是操作系统为了公平分配处理器时间而进行的操作,频繁的上下文切换会带来额外的开销。通过观察线程的切换频率,开发者可以判断是否需要调整线程优先级...
一个进程中可以有多个线程,它们共享进程的资源,但拥有各自的执行上下文。在C#中,`System.Threading`命名空间提供了对线程操作的支持。 倒计时程序的核心在于定期检查时间并更新显示。使用线程实现倒计时,我们...
4. **线程上下文切换**:展示线程的上下文,如调用堆栈,帮助理解线程的执行路径。 5. **实时更新**:实时监控线程状态的变化,以便于调试和性能分析。 `MFCMultiThread` 文件可能是包含实现这些功能的 MFC 示例...
2. **详细信息**:每个线程的详细信息,如线程的基地址、线程上下文、线程栈跟踪,这有助于定位代码执行路径。 3. **筛选和排序**:允许用户根据特定条件(如线程ID、CPU使用率或状态)筛选和排序线程,以便快速...
- **线程的定义**:线程是程序执行的最小单位,每个线程都有自己的执行上下文和栈空间,共享同一进程的内存资源。 - **线程创建**:C#通过`System.Threading.Thread`类来创建线程,通过实例化`Thread`类并传递一个...
在Java中,有多种类加载器,包括系统类加载器、当前类加载器和线程上下文类加载器。系统类加载器,通常由`ClassLoader.getSystemClassLoader()`获取,主要负责加载启动应用时由classpath指定的类。由于它与JVM启动...
在设计多线程文件搜索器时,还需要考虑性能优化,如避免过多的磁盘I/O操作,合理分配线程数量以充分利用CPU资源,以及有效地管理内存和线程上下文切换。 总的来说,“多线程文件搜索器”利用了多线程技术提高了...
- **CurrentContext**:提供当前线程所属的上下文信息。这对于理解和管理线程在其生命周期内的状态转移至关重要。 #### 三、以ThreadStart方式实现多线程 - **ThreadStart**:这是一种创建线程的方法,适用于没有...
**OpenGL共享上下文**是OpenGL编程中的一个重要概念,允许多个线程共享相同的OpenGL状态和资源,如顶点数组、纹理、着色器等。这样做的好处是可以避免重复初始化和资源分配,提高效率,同时确保在多个线程之间安全地...
4. **上下文切换**:线程切换涉及到上下文切换,即保存当前线程的状态(如寄存器值、程序计数器等),恢复下一个要运行线程的状态。上下文切换是有开销的,包括CPU时间、内存资源以及可能的数据同步成本。 5. **...
7. **性能优化**:尽管多线程能提高程序性能,但过多的线程会消耗大量系统资源,可能导致上下文切换开销。合理评估并限制线程数量,确保线程生命周期的管理。 8. **博客内容结合**:描述中提到结合了博客文章内容,...
线程在进程的上下文中运行,共享进程的内存空间和其他资源,但拥有自己独立的执行栈和程序计数器。这种轻量级的特性使得线程相比进程创建和销毁的开销更小,更适合用于实现并发。 2. **线程的创建** 创建Java线程...
8. **性能优化**:可能涵盖了如何平衡多线程的使用以优化程序性能,比如避免过多的线程创建和销毁,减少上下文切换的开销。 9. **异常处理**:多线程环境下如何进行有效的异常处理,以防止整个应用程序崩溃。 10. ...
线程是操作系统中的基本执行单元,它在一个进程中独立运行,拥有自己的执行上下文,包括程序计数器、寄存器和栈。一个进程可以包含多个线程,它们共享同一地址空间,但拥有各自的执行路径。查看进程中的线程可以帮助...
在 Flask 中,每个线程都有自己的请求上下文和应用上下文。 - **Local**:该类是 `LocalStack` 的基类,提供了 `_storage_` 和 `_ident_func_` 这两个关键属性: - `_storage_`:用于存储每个线程的数据。 - `_...
而多线程处理器设计使得硬件能支持线程上下文的完整存储,无需额外的保存和恢复操作,实现了线程切换的零周期开销。 ### MIPS多线程技术的具体实现 MIPS科技采用的多线程技术主要包括虚拟处理单元(VPE)和线程上...
创建和管理线程都有一定的开销,包括上下文切换的时间、内存使用等。因此,在设计时需要权衡线程的数量和效率。 ##### 2.2 创建一个线程 在iOS中,可以使用多种方式创建线程: - **2.2.1 使用NSThread**:直接使用...
- **调度与上下文切换:**线程的调度和上下文切换比进程更为轻量级,因为它们共享相同的地址空间,无需频繁地在不同的地址空间之间切换。 4. **调度机制:** - 进程的调度由操作系统负责,通常基于进程的优先级和...