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

目录
相关文章
|
8月前
|
算法
出现线程死锁缺陷一般有那些原因?该怎么解决?
出现线程死锁缺陷一般有那些原因?该怎么解决?
89 1
|
8月前
|
测试技术
如何避免测试同化现象?
如何避免测试同化现象?
|
缓存 Java
一例JAVA多线程访问卡死的现象
一例JAVA多线程访问卡死的现象
176 0
|
算法 Java Linux
如果面试官让你分析类初始化阶段的死锁现象
哈喽,我是子牙。十余年技术生涯,一路披荆斩棘从技术小白到技术总监到JVM专家到创业。技术栈如汇编、C语言、C++、Windows内核、Linux内核。特别喜欢研究虚拟机底层实现,对JVM有深入研究。分享的文章偏硬核,很硬的那种。
102 0
如果面试官让你分析类初始化阶段的死锁现象
|
Java
本来想用“{{”秀一波,结果却导致了内存溢出!(上)
本来想用“{{”秀一波,结果却导致了内存溢出!
135 0
本来想用“{{”秀一波,结果却导致了内存溢出!(上)
|
Java API
本来想用“{{”秀一波,结果却导致了内存溢出!(下)
本来想用“{{”秀一波,结果却导致了内存溢出!
179 0
本来想用“{{”秀一波,结果却导致了内存溢出!(下)
|
存储 Java
一个极易被忽略的内存泄漏情况,看看你会不会犯一样的错
Java之所以能够成为世界上最受欢迎的语言,与其垃圾回收机制分不开。我们Javaer能够在创建完对象后就不用管她的生死,确实是十分方便(真特么是个渣男)。可是有时候因为你创建了她,又对她爱答不理,就很有可能出大问题。
分享一个诡异的可见性问题
分享一个诡异的可见性问题
116 0
分享一个诡异的可见性问题
|
Java 程序员
一个多线程死锁案例,如何避免及解决死锁问题?
多线程死锁在java程序员笔试的时候时有遇见,死锁概念在之前的文章有介绍,大家应该也都明白它的概念,不清楚的去翻看历史文章吧。
224 0
一个多线程死锁案例,如何避免及解决死锁问题?
|
存储 SQL Web App开发
你们的系统真的安全吗?你们系统是不是也有这种诡异的情况呢?
这个文章来源灵感是公司的项目在安全检查的时候,发现了很多 SQL 注入的一些问题还有一些 XSS 攻击问题,SQL 注入我们都是比较了解的,那么什么是 XSS 攻击呢?测试反馈给我的时候我是一脸懵逼呀,现在我们就来说一下什么是 XSS 攻击,以及我们怎么防止它的出现。

相关实验场景

更多