ContextClassLoader使用不当导致的"奇怪"现象

简介:

最近从一位同学那得知了一个奇怪的异常现象(和classloader相关), 问题一直没有解决, 所以趁周末有空搭建测试环境研究一下:

问题现象

该应用是服务型应用, 如果应用启动时初始化所有的spring bean, 那么会有如下的异常,反之运行时一点一点初始化spring bean则没有任何问题。(所以应用在线上没有使用fail fast模式,有一定风险)
_2015_08_11_6_58_17

个人分析

  • 异常比较底层, 所以把抛出异常的class(DirContextURLStreamHandler)反编译看了下。如下:
    _2015_08_11_7_10_52
  • 先了解了下该class的结构和用途:该类中clBindings和threadBindings是普通的map对象, 当执行ServletContext.getResource(resourceName).openConnection()方法后DirContextURLStreamHandler的get方法都会调用(基本可认为只要加载web应用下文件就会使用). 为什么这个方法调用会和异常堆栈联系在一起? 这里简单讲解下tomcat的加载应用中资源的逻辑, 当我们使用ServletContext.getResource获取该资源URL时,你可能看到如下的结果, 因为tomcat 使用了Java命名系统接口(jndi)来命名/操作应用下的资源:
    _2015_08_08_12_03_37
  • jndi这个协议的Handler是通过以下语句设置的(WebappLoader.java中):
    URL.setURLStreamHandlerFactory(new DirContextURLStreamHandlerFactory);
  • 而DirContextURLStreamHandlerFactory实现也比较简单,如果url的协议是jndi, 就由DirContextURLStreamHandler(这也是最终抛出异常的类)来进行处理。
    _2015_08_11_8_12_47

从异常中发现,加载webx中的uri.xml时抛出了异常

定位问题

_2015_08_11_7_10_52

  • 尝试在class中增加了断点。当抛出异常时, 上图中115行的result为nulll, 正常情况下result是有值的。
    出现异常时clBindings中包含的key是org.jboss.web.tomcat.tc5.WebCtxLoader$ENCLoader, 而currentCL(上下文classloader)是org.jboss.mx.loading.UnifiedClassLoader3, 而导致查询不到结果. 所以我开始怀疑,是否业务代码中将上下文的classloader给改掉了。而导致这里查询不到DirContext(web目录上下文对象).
  • 用btrace打印了所有调用Thread.setContextClassLoader(ClassLoader classloder)的点, 并且该方法参数的class是org.jboss.mx.loading.UnifiedClassLoader3的线程堆栈. 日志打印出来后,内容很多,但是稍微过滤后(堆栈前5 frame中包含alibaba or taobao关键字),可以最终只剩下几个可疑class:
  • com.taobao.hsf.route.strategy.groovy.GroovyRouteRuleParser.parse(GroovyRouteRuleParser.java:103(正常)
    _2015_08_11_8_25_03

这里在操作ContextClassLoader逻辑是: 取出->更改->还原(finally语句块)

  • com.taobao.agoo.open.OpenServiceFactory.init(OpenServiceFactory.java:57)(有问题)
    _2015_08_11_8_26_05

这里在操作ContextClassLoader逻辑是: 取出->更改

解决问题

  • 很明显了,只要在OpenServiceFactory.java中增加"还原"的操作就可以了。增加"还原"操作后,应用启动初始化所有bean正常, 且无异常抛出。

问题还原

  • OpenServiceFactory更改了ContextClassLoader,但是没有还原原有的ContextClassLoader这种写法很少见(我不确定代码这样写的原有),但这里确实导致了该应用启动时抛出的异常。
  • 应用启动抛出异常 真实的原因是 OpenServiceFactory的初始化比较早,导致后面初始化home.webx.xml(还有其他的xml)出现了加载不到的异常。如果OpenServiceFactory初始化比较晚,并且应用不需要加载web下的资源文件,那么也不会有问题。
  • 那为什么应用启动时,不初始化OpenServiceFactory就没有问题呢?因为该应用是服务型的应用,启动时已经初始化过了所需要的web目录下资源文件(例如: webx.xml, pipeline.xml等), 没有其他的资源文件需要加载(即使影响也肯能只影响修改了ContextClassLoader的线程), 所以不会导致这个问题出现。

classloader引起的问题很少见,问题出现后也不太容易排查和解决,这个场景相对是比较简单,只是这个异常比较"奇怪"而已

目录
相关文章
|
7月前
|
算法
出现线程死锁缺陷一般有那些原因?该怎么解决?
出现线程死锁缺陷一般有那些原因?该怎么解决?
84 1
|
7月前
|
设计模式 存储 算法
谈谈代码:如何避免写出糟糕if...else语句
在写代码的日常中,`if...else`语句是极为常见的.正因其常见性,很多同学在写代码的时候并不会去思考其在目前代码中的用法是否妥当.而随着项目的日渐发展,糟糕的`if...else`语句将会充斥在各处,让项目的可维护性急剧下降.故在这篇文章中,笔者想和大家谈谈如何避免写出糟糕`if...else`语句.
50 0
谈谈代码:如何避免写出糟糕if...else语句
|
7月前
|
测试技术
如何避免测试同化现象?
如何避免测试同化现象?
代码优雅之道——如何干掉过多的if else
代码优雅之道——如何干掉过多的if else
131 0
|
缓存 Java
一例JAVA多线程访问卡死的现象
一例JAVA多线程访问卡死的现象
169 0
程序人生 - 创可贴使用不当或致截肢
程序人生 - 创可贴使用不当或致截肢
109 0
程序人生 - 创可贴使用不当或致截肢
|
算法 Java Linux
如果面试官让你分析类初始化阶段的死锁现象
哈喽,我是子牙。十余年技术生涯,一路披荆斩棘从技术小白到技术总监到JVM专家到创业。技术栈如汇编、C语言、C++、Windows内核、Linux内核。特别喜欢研究虚拟机底层实现,对JVM有深入研究。分享的文章偏硬核,很硬的那种。
98 0
如果面试官让你分析类初始化阶段的死锁现象
|
Java
本来想用“{{”秀一波,结果却导致了内存溢出!(上)
本来想用“{{”秀一波,结果却导致了内存溢出!
133 0
本来想用“{{”秀一波,结果却导致了内存溢出!(上)
|
Java API
本来想用“{{”秀一波,结果却导致了内存溢出!(下)
本来想用“{{”秀一波,结果却导致了内存溢出!
176 0
本来想用“{{”秀一波,结果却导致了内存溢出!(下)
|
存储 Java
一个极易被忽略的内存泄漏情况,看看你会不会犯一样的错
Java之所以能够成为世界上最受欢迎的语言,与其垃圾回收机制分不开。我们Javaer能够在创建完对象后就不用管她的生死,确实是十分方便(真特么是个渣男)。可是有时候因为你创建了她,又对她爱答不理,就很有可能出大问题。

相关实验场景

更多