【hello,world 也打脸】记storm-starter在某知名IDE下的悲催调试经历
背景
最近收到这样一个问题:
Storm处理消息时会根据Topology生成一棵消息树,Storm如何跟踪每个消息、如何保证消息不丢失以及如何实现重发消息机制?
虽已回复,但心想还是看下storm这块的源码吧.那看静态多不爽啊,那总得调试吧,好吧,造个本地环境来调吧。
先看看maven的build过不过:
mvn -f pom.xml clean install
搞定storm的编译打包,接着是storm-starter的编译打包,一切很顺利啊,跑一下看看:
${STORM_HOME}/bin/storm jar ${STORM_JAR} ${STORM_STARTER_JAR} storm.starter.WordCountTopology
顺利出结果了,不就是个hello world嘛!
接着造本地环境吧,将storm-starter的源码按maven方式导入Intellij IDEA,注意,从这时候悲催就开始了。
hello,world 打脸了
导入IDE后,兴致勃勃的点了F5
,然后:
这尼玛,说好不打脸的!
看了又看依赖“都合适”啊,ClassPath“都合适”啊,否则编译不通过啊,为毛跑!不!起!来!
这同样的操作,在eclipse里妥妥的啊,各种能跑啊,为毛在Intellij IDEA里出错了呢?
异常是如何产生的
好吧,既然打脸了,又是知名IDE的粉丝,坚决要知耻而后勇的。
那么,看下异常如何产生的吧。
上面的图,基本概括了异常NoClassDefFound
产生的路径。
更细节的异常产生情况如下:
注意看调用栈:
JVM_GetClassDelaredMethods
是JVM内方法,在找入口函数main
的过程中,此方法被调用。- 接着,此方法会调用验证字节码的过程:
verify_code
。- 发现有用到
backtype.storm.topology.IRichBolt
接口,那么找找这个接口所在的.class
文件吧:
如果我们运行java -cp . xxx
,通过-cp
或者-classpath
参数指定了classpath
,那么这个.class
就会被找到。然后进入parse
的过程。
- 悲催的是,IDEA的运行或者调试命令
f5->run
,没有将backtype/storm/topology/IRichBolt
所在的jar包storm-core.jar
加入classpath,这都是后话了...
那么,既然有个找*.class
的过程,这个过程如下:
对上图做一点简要说明:
- 执行java -cp . $mainClass .
java
程序(这里指java这个程序本身)的入口函数main
,会创建虚拟机JVM
实例,过程中会初始化JVM
本地ClassLoader
.- 在
JVM
寻找.class
文件时,调用ClassLoader::load_classfile
方法,从jar包、zip包、目录中寻找指定的.class
文件 .- 本文中,木有找到
backtype/storm/topology/IRichBolt.class
,所以会置一个延时异常__pending_exception
,这个异常关联了这个类(接口)、文件名、异常的类型(NoClassDefFound
),那这个异常什么时候处理呢?后文再说.
看下这个异常的内容:
异常是如何处理的
好了,异常的产生清楚了,还有个问题,那个__pending_exception
是何时被处理呢?
看下图:
对上图做下简要说明:
- 执行
java -cp . $mainClass
.- java程序的入口函数
main
,在层层初始化的过程中,会调用到LoadMainClass
函数, 结合本文的第一幅图就可以知道,这个函数最终会制造那个NoClassDefFound
的异常__pending_exception
,然后返回的是一个空的mainClass
.- 紧跟着
LoadMainClass
函数,是一个CHECK_EXCEPTION_NULL_LEAVE
的宏,这个宏展开后,会处理上面制造的那个异常,然后,打印异常信息。这里就是那个被打脸的异常了。
为什么被打了脸
这里,异常产生的本质和异常处理,清楚了。
简单的概括下就是:
/* 伪代码 */
main /* java这个程序的main */
-> createJVM() /* 创建JVM */
-> loadMainClass() /* 加载我们指定的$mainClass文件,这是个class文件 */
-> findMethod("main") /* 在$mainClass中找main方法,java写的程序的main */
-> getMethodFromJVM() /* 没缓存,问JVM要 */
-> classLoader.loadFromFile() /* 在classpath中找.class文件 */
-> 没找到,置异常NoClassDefFound.
但是,Intellij IDEA为何在运行时不将storm-core.jar
包含进classpath
呢?
换句话说:为啥被打脸??
打脸的理由很简单:
F5->run
,先make/compile/build,再运行.- 依赖的scope设为了
provided
,此设置仅在编译阶段将依赖的jar包加入classpath,在运行阶段,不会将jar包加入classpath.
解决的方法也非常简单:
如果不是通过mvn来运行,而是在IDE下调试/运行,赶紧将依赖的jar包的scope选为
compile
吧,妥妥的不会被打脸!
看下是不是妥妥的呢?
后记
.