java程序启动的时候报beancreationexception异常_Arthas + jdb 启动时诊断调试案例分析...

Arthas在处理应用启动时相关疑难杂症显得捉襟见肘,jdb是java命令行调试工具,具体的调试功能和IDE相关调试几乎一致,接下来试着玩一玩Arthas和jdb工具在一起能学到什么新的知识。

文章写的比较急促,大段代码无法完整显示。

准备

Arthas安装

在 Github 上找到最新的发布包:https://github.com/alibaba/arthas/releases

$ wget https://github.com/alibaba/arthas/releases/download/arthas-all-3.3.2/arthas-3.3.2-bin.zip$ unzip arthas-3.2.2-bin.zip -d arthas$ cd arthas# 帮助$ java -jar arthas-boot.jar -h

如何使用

# 进入交互页面$ java -jar arthas-boot.jar[INFO] arthas-boot version: 3.2.2[INFO] Process 62498 already using port 3658[INFO] Process 62498 already using port 8563[INFO] Found existing java process, please choose one and hit RETURN.* [1]: 62498 xxx Java 进程 xxx[2]: 54006 xxx Java 进程 xxx[3]: 53993 xxx Java 进程 xxx> 选择一个 Java 进程ID,进入交互页面[INFO] arthas home: ~/.arthas[INFO] The target process already listen port 3658, skip attach.[INFO] arthas-client connect 127.0.0.1 3658,---.  ,------. ,--------.,--.  ,--.  ,---.   ,---./  O  \ |  .--. ''--.  .--'|  '--'  | /  O  \ '   .-'|  .-.  ||  '--'.'   |  |   |  .--.  ||  .-.  |`.  `-.|  | |  ||  |\  \    |  |   |  |  |  ||  | |  |.-'    |`--' `--'`--' '--'   `--'   `--'  `--'`--' `--'`-----'

启动任一你的应用,在启动参数中加入,具体参数可以参考其他资料

-agentlib:jdwp=transport=dt_socket,address=8000,server=y,suspend=y

这里主要提一下suspend=y,代表应用启动时会默认阻塞,等待jdb连接,一般IDE在启用debug模式时,suspend=n,自动连接到应用,比如IDEA的debug模式在log下会显示:

-agentlib:jdwp=transport=dt_socket,address=127.0.0.1:56645,suspend=y,server=nConnected to the target VM, address: '127.0.0.1:56645', transport: 'socket'

在正常启动模式下,加入debug启动参数后,应用阻塞在启动方法中。

Listening for transport dt_socket at address: 8000

在很多需要从启动环节开始对进行应用诊断,可以在该节点启动Arthas,并提示选择对应的应用,阻塞在main方法的应用无法在提示列表正确显示对应的应用启动名,会显示。

-- main class information unavailable

选择对应的应用,Arthas会等待一段时间attach并提示。

[INFO] arthas home: /Users/min_xu/.arthas/lib/3.3.2/arthas[INFO] Try to attach process 1389[ERROR] Start arthas failed, exception stack trace:com.sun.tools.attach.AttachNotSupportedException: Unable to open socket file: target process not responding or HotSpot VM not loaded  at sun.tools.attach.BsdVirtualMachine.(BsdVirtualMachine.java:90)  at sun.tools.attach.BsdAttachProvider.attachVirtualMachine(BsdAttachProvider.java:63)  at com.sun.tools.attach.VirtualMachine.attach(VirtualMachine.java:208)  at com.taobao.arthas.core.Arthas.attachAgent(Arthas.java:84)  at com.taobao.arthas.core.Arthas.(Arthas.java:28)  at com.taobao.arthas.core.Arthas.main(Arthas.java:124)[ERROR] attach fail, targetPid: 1389

原因在于通过attach 系统管道通信连接到待调试的应用响应超时,此时,我们必须要快速进入jdb命令行工具进入交互环节。

min_xu@xumindeMacBook-Pro Downloads % jdb -connect com.sun.jdi.SocketAttach:port=8000,hostname=127.0.0.1设置未捕获的java.lang.Throwable设置延迟的未捕获的java.lang.Throwable正在初始化jdb...>VM 已启动: 当前调用堆栈上没有帧

此时,jdb调试工具正式连接到应用程序。

到此为止,Arthas + jdb完成了应用的连接,结束。

......

等等,今天我们想解决的是什么问题。

对,本地执行一直正常的程序打包到线上提示 NoSuchMethodError。

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'xxx': Injection of resource dependencies failed;  nested exceptsocket/config/socketio/SocketIoConfig.class]:  Bean instantiation via factory method failed; n ested exception is org.springframework.beans.BeanInstantiationException:  Failed to instantiate [com.corundumstudio.socketio.SocketIOServer]:  Factory method 'socketIOServer' threw exception;  nested exception is java.lang.NoSuchMethodError:  io.netty.handler.codec.compression.ZlibCodecFactory.isSupportingWindowSizeAndMemLevel()Z  at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessPropertyValues(CommonAnnotationBeanPostProcessor.java:321)ion is 

第一轮

启动时异常可以通过在main方法中进行阻塞式调用,防止进程退出同样可以达到目的。

public static void main(String[] args) throws IOException {        try {            SpringApplication.run(SocketApplication.class, args);        } catch (Throwable e) {            e.printStackTrace();        }        // block 保证进程不退出        System.in.read();    }

然而今天的目的是为了玩一玩 jdb,使用jdb的阻塞机制完成Arthas的attach,为了完成这个功能,需要开启两个shell会话窗口。

首先,需要解决掉Arthas attach到应用超时异常问题,既然阻塞住了应用,那我们需要让程序跑来,没错 jdb命令行窗口

run

......

等等,程序直接运行到Error退出了。

既然是阻塞调用,我们先尝试给这个类执行错误之前打个断点,分析源码。

public final class ZlibCodecFactory {    private static final int DEFAULT_JDK_WINDOW_SIZE = 15;    private static final int DEFAULT_JDK_MEM_LEVEL = 8;    private static final boolean noJdkZlibDecoder;    private static final boolean noJdkZlibEncoder;    private static final boolean supportsWindowSizeAndMemLevel;    static {        noJdkZlibDecoder = SystemPropertyUtil.getBoolean("io.netty.noJdkZlibDecoder",                PlatformDependent.javaVersion() < 7);        logger.debug("-Dio.netty.noJdkZlibDecoder: {}", noJdkZlibDecoder);        noJdkZlibEncoder = SystemPropertyUtil.getBoolean("io.netty.noJdkZlibEncoder", false);        logger.debug("-Dio.netty.noJdkZlibEncoder: {}", noJdkZlibEncoder);        supportsWindowSizeAndMemLevel = noJdkZlibDecoder || PlatformDependent.javaVersion() >= 7;    }    /**     * Returns {@code true} if specify a custom window size and mem level is supported.     */    public static boolean isSupportingWindowSizeAndMemLevel() {        return supportsWindowSizeAndMemLevel;    }

ZlibCodecFactory类内部存在一个static 语句块,调用Error的是静态方法isSupportingWindowSizeAndMemLevel,按照类加载的7个步骤顺序,static语句块需要在类初始化阶段触发,而后被isSupportingWindowSizeAndMemLevel被调用,所以给static语句块 37行加上一个断点,重新上述的步骤。

第二轮

min_xu@xumindeMacBook-Pro Downloads % jdb -connect com.sun.jdi.SocketAttach:port=8000,hostname=127.0.0.1设置未捕获的java.lang.Throwable设置延迟的未捕获的java.lang.Throwable正在初始化jdb...>VM 已启动: 当前调用堆栈上没有帧> stop at io.netty.handler.codec.compression.ZlibCodecFactory:37正在延迟断点io.netty.handler.codec.compression.ZlibCodecFactory:37。将在加载类后设置。

在此启动Arthas,attach到对应的应用。

Arthas依旧阻塞在了attach过程中不能自拔,继续让jdb run。

#jdb run> run

第二轮,诊断程序执行到异常,退出!Arthas 停止阻塞进入到调试阶段,随着诊断程序的异常退出,Arthas紧跟着退出了。

当你绝望沮丧的时候,就是救星出场的时候,Arthas通过OS 管道通信连接到诊断应用,应用内部一定是通过某个非阻塞的线程完成,jdb断点是不是可以仅针对某个线程阻塞呢?答案是肯定的,jdb以及IDEA默认的debug阻塞默认都是所有线程 All Thread,在jdb中,改名了是suspend {threadId} + run 命令组合。

260cd18057aa50d6e29c095940b92443.png

ee97ee7ac66b663228308db5033f051c.png

第三轮

这次我们根据jdb断点提示,把断点打在类加载的方法上(后面有解释)。

min_xu@xumindeMacBook-Pro Downloads % jdb -connect com.sun.jdi.SocketAttach:port=8000,hostname=127.0.0.1设置未捕获的java.lang.Throwable设置延迟的未捕获的java.lang.Throwable正在初始化jdb...>VM 已启动: 当前调用堆栈上没有帧main[1] stop in io.netty.handler.codec.compression.ZlibCodecFactory.()正在延迟断点io.netty.handler.codec.compression.ZlibCodecFactory.()。将在加载类后设置。main[1] threads #查看所有线程组system:  (java.lang.ref.Reference$ReferenceHandler)0x156 Reference Handler正在执行条件等待  (java.lang.ref.Finalizer$FinalizerThread)0x155  Finalizer        正在执行条件等待  (java.lang.Thread)0x154                         Signal Dispatcher正在运行组main:  (java.lang.Thread)0x1                           main             正在运行

Arthas 抓紧上车了,在另外一个Shell窗口启动Arthas,attach到诊断程序。

main[1] suspend 0x1main[1] run> resume 0x1> 设置延迟的断点io.netty.handler.codec.http.websocketx.extensions.compression.PerMessageDeflateServerExtensionHandshaker:54断点命中: "线程=main", io.netty.handler.codec.http.websocketx.extensions.compression.PerMessageDeflateServerExtensionHandshaker.<init>(), 行=54 bci=0main[1] suspend 0x1main[1] runmain[1] print io.netty.handler.codec.compression.ZlibCodecFactory.isSupportingWindowSizeAndMemLevel()com.sun.tools.example.debug.expr.ParseException: Name unknown: io.netty.handler.codec.compression.ZlibCodecFactory.isSupportingWindowSizeAndMemLevel io.netty.handler.codec.compression.ZlibCodecFactory.isSupportingWindowSizeAndMemLevel() = 空值main[1] next>已完成的步骤: "线程=main", io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler.<init>(), 行=34 bci=28main[1] print io.netty.handler.codec.compression.ZlibCodecFactory.isSupportingWindowSizeAndMemLevel() io.netty.handler.codec.compression.ZlibCodecFactory.isSupportingWindowSizeAndMemLevel() = truemain[1]

按照上述的逻辑,Arthas终于连接上诊断程序。

jdb分别断点在方法前后,Arthas分别完成了ZlibCodecFactory加载完成前后的调用,在ZlibCodecFactory加载完成后,可以清楚得看到ZlibCodecFactory加载的几乎所有细节内容。

[arthas@3157]$ jad io.netty.handler.codec.compression.ZlibCodecFactoryNo class found for: io.netty.handler.codec.compression.ZlibCodecFactoryAffect(row-cnt:0) cost in 18 ms.[arthas@3157]$ jad io.netty.handler.codec.compression.ZlibCodecFactoryNo class found for: io.netty.handler.codec.compression.ZlibCodecFactoryAffect(row-cnt:0) cost in 4 ms.[arthas@3157]$ jad io.netty.handler.codec.compression.ZlibCodecFactoryClassLoader:+-sun.misc.Launcher$AppClassLoader@18b4aac2  +-sun.misc.Launcher$ExtClassLoader@7f424ec2Location:/Users/xxxx/.m2/repository/io/netty/netty-codec/4.1.32.Final/netty-codec-4.1.32.Final.jar

一般静态语句块是一个jdb断点的好标记,如果没有static怎么办,如何定位到init的方法是关键。

stop in MyClass.<init> (<init>标识MyClass的构造函数)stop in MyClass.<clinit> (<clinit>标识MyClass的静态初始化代码)

查看一下完整的jdb调试过程。

97ea375f4d6d05d8440e347140d5293d.png

查看下Arthas的完成操作过程,注意:这两个窗口是在交叉联调。

fd01159d09aefaf94f2eb6e6f0a0ca74.png

一次有意思的Arthas + jdb调试过程完成,最后回答一下自问问题,都已经能jdb debug了,还需要arthas干啥呢:arthas解决了更多的透明度工具。

jdb提供了一系列print工具,可以再jdb shell中查看

print支持多种包含方法调用的简单Java表达式,例如:print MyClass.myStaticFieldprint myObj.myInstanceFieldprint i + j + k (i, j, k are primities and either fields or local variables)print myObj.myMethod() (if myMethod returns a non-null)print new java.lang.String("Hello").length()

回到上面的问题,jdb在断点前后同样可以使用print工具查看内置的一些程序状态。

main[1] print io.netty.handler.codec.compression.ZlibCodecFactory.isSupportingWindowSizeAndMemLevel()com.sun.tools.example.debug.expr.ParseException: Name unknown: io.netty.handler.codec.compression.ZlibCodecFactory.isSupportingWindowSizeAndMemLevel io.netty.handler.codec.compression.ZlibCodecFactory.isSupportingWindowSizeAndMemLevel() = 空值main[1] next>已完成的步骤: "线程=main", io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler.<init>(), 行=34 bci=28main[1] print io.netty.handler.codec.compression.ZlibCodecFactory.isSupportingWindowSizeAndMemLevel() io.netty.handler.codec.compression.ZlibCodecFactory.isSupportingWindowSizeAndMemLevel() = truemain[1]

那Arthas jad可以给我们提供哪些更有价值的信息呢?

[arthas@64343]$ sc -d io.netty.handler.codec.compression.ZlibCodecFactory class-info        io.netty.handler.codec.compression.ZlibCodecFactory code-source       /Users/min_xu/.m2/repository/io/netty/netty-codec/4.1.32.Final/netty-codec-4.1.32.Final.jar name              io.netty.handler.codec.compression.ZlibCodecFactory isInterface       false isAnnotation      false isEnum            false isAnonymousClass  false isArray           false isLocalClass      false isMemberClass     false isPrimitive       false isSynthetic       false simple-name       ZlibCodecFactory modifier          final,public annotation interfaces super-class       +-java.lang.Object class-loader      +-java.net.URLClassLoader@108941f3                     +-sun.misc.Launcher$AppClassLoader@18b4aac2                       +-sun.misc.Launcher$ExtClassLoader@6e1e0d4c classLoaderHash   108941f3

如果需要查看更详细的Feild信息,增加-f

fields            name     logger                   type     io.netty.util.internal.logging.InternalLogger                   modifier final,private,static                   value    LocationAwareSlf4JLogger(io.netty.handler.codec.compression.ZlibCodecFactory)                   name     DEFAULT_JDK_WINDOW_SIZE                   type     int                   modifier final,private,static                   value    15                   name     DEFAULT_JDK_MEM_LEVEL                   type     int                   modifier final,private,static                   value    8                   name     noJdkZlibDecoder                   type     boolean                   modifier final,private,static                   value    false                   name     noJdkZlibEncoder                   type     boolean                   modifier final,private,static                   value    false                   name     supportsWindowSizeAndMemLevel                   type     boolean                   modifier final,private,static                   value    true

在启动时可以通过Arthas查看更多案例,如果仅仅是类加载问题,可以更简单的使用JVM参数 -XX:+TraceClassLoading从日志中跟踪。

本次玩耍结束,祝玩得开心。

更多玩法:jdb help

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值