一.线上 stackOverFlowError

记一次 stackoverflowerror 线上排查进程

xxx日,突然收到线上日志关键字频繁告警 classCastException.从字面上的报警来看,仅仅是类型转换异常,查看细则发现其实是 stackOverFlowError.很多同学面试的时候总会被问到有没有遇到过线上stackOverFlowError?有么有遇到后栈溢出?今日他来了,他带着问题走来了.话不说多,直入正题.详细细则如下

记一次 stackoverflowerror 线上排查进程

二.优先线上问题处理

记一次 stackoverflowerror 线上排查进程

请原谅我抽象画风

temp 计划.首要的线上的稳定性肯定是第一要义,客户可不会等你长篇大论抓包,剖析,debug.过了30min还不康复,本钱的大刀就要砍到你身上了.所以我们先想到的是代码回退,镜像回滚处理问题优先.虽然说是临时计划,那这时候我觉得这可能是最重要的最佳计划.究竟老镜像是不会出任何问题的.

三.持续深化剖析

处理完线上的问题后,先从外层的堆栈打印来看,找到 ClassCastException 这儿找到真实的原因,究竟退下来的不仅仅是坏代码,还有需求迭代的正常需求仍是需求持续推上去上线.

3.1 整体的流程整理

记一次 stackoverflowerror 线上排查进程

找到报错第一步:

3.1.1 step1: classCastException

先表象开始剖析

记一次 stackoverflowerror 线上排查进程
记一次 stackoverflowerror 线上排查进程

从这儿可以看到判断了是否为 Throwable 类型.假如是就进行 Exception 强转.这儿就要温习一下了.

记一次 stackoverflowerror 线上排查进程

StackOverFlowError 承继 Error ,ErrorThrowable 承继而来. Exception 则是另外的分支. 关于 ErrorException 也有通行的原则. Exception 一般是程序顶用以来抛出程序异常所运用的且一般是可以经过编码优化来处理的,或是用来 try catch exception 来进行捕获处理的. Error 则是用来表达程序运行期间呈现的严重错误,这时候通常是jvm级别的.如常见的OutOfMemoryError,stackOverFlowError.等.通常则是无法经过代码来进行捕获的.

有了这些基础知识后,再回来这儿虽然StackOverFlowErrorException都承继于 Throwable .但这是两个子的实现,无法做到强转.由之得到了 ClassCastException .后边这便是转成了 ClassCastException .这个类则是承继自 Exception .经过 try catch 捕获异常后,得到了正常的日志打印,也便是收到的日志告警. 然后这仅仅是表现.根因还没有找到.

当然这段代码也需求进行优化.假如得到的是Error的类型就要对应的进行Error的处理而不是仅仅对Throwable都统一强转为Exception 代码优化

 Exception exception = null;
 f(ar instanceof Error){
      Error arError=(Error)ar; 
      exception=new Exception (arError);
 }else if(ar instanceof Exception){
      exception = (Exception) ar;
 }

3.1.2 step2:事情远没有结束,到底是哪里出问题 StackOverFlowError

本质上仍是由于StackOverFlowError才得到的如上的 ClassCastException. 回想下 JVM 的内存布局(如下图)

记一次 stackoverflowerror 线上排查进程

能产生 StackOverFlowError 只有在线程私有的 stack(native method stack | virtual method stack) 这儿.这儿通常产生这个错误的原因是由于办法调度的深度过长了或是线程自身分别的内存太小不足以支撑现在的复杂调用.

  • 第一种场景:常见的如递归调用.
  • 第二种场景: jvm 在1.5 之后默许的xss 大小默许为 1m.一般场景下支撑1000-2000个深度调用没问题.包括递归.(没试过.数值参阅自:深化了解java虚拟机)

3.1.3 找到问题比照代码

从一般情况下第二种场景不太可能呈现.仍是回到递归调用引起的.排查代码.花不多少,看代码,经过比照版别之间diff(比照时间稍微有点长).简略如下:

无问题代码

private static void error(Logger logger, String message, Object... arg) {
        if (isLogOn(LogLevelEnum.ERROR, logger)) {
            if (arg != null && arg.length > 0 && arg[0] instanceof Throwable) {
                logger.error(message, arg[0]);
            } else {
                logger.error(message, arg);
            }
            TRACER_LOGGER.error(message, arg);
        }
    }
public static void error(Object... arg) {
        String message = getMessage("{}", 4, arg);
        error(getSoaErrorLogger(), message, arg);
    }
    public static void error(String message, Object... arg) {
        message = getMessage(message, 4, arg);
        error(getSoaErrorLogger(), message, arg);
    }

代码优化后的代码 有问题版

private static void error(Logger logger,String realMessage, String message, Object... arg) {
        if (isLogOn(LogLevelEnum.ERROR, logger)) {
            if (arg != null && arg.length > 0 && arg[0] instanceof Throwable) {
                logger.error(message, arg[0]);
            } else {
                logger.error(message, arg);
            }
            TRACER_LOGGER.error(message, arg);
        }
    }
public static void error(Object... arg) {
        String message = getMessage("{}", 4, arg);
        error(getSoaErrorLogger(), message, arg);
    }
    public static void error(String message, Object... arg) {
        message = getMessage(message, 4, arg);
        final String realMessage=message;
        error(getSoaErrorLogger(),realMessage, message, arg);
    }

代码优化后的代码 完善版

private static void error(Logger logger,String realMessage, String message, Object... arg) {
        if (isLogOn(LogLevelEnum.ERROR, logger)) {
            if (arg != null && arg.length > 0 && arg[0] instanceof Throwable) {
                logger.error(message, arg[0]);
            } else {
                logger.error(message, arg);
            }
            TRACER_LOGGER.error(message, arg);
        }
    }
public static void error(Object... arg) {
        String message = getMessage("{}", 4, arg);
        final String realMessage=message;
        error(getSoaErrorLogger(),realMessage, message, arg);
    }
    public static void error(String message, Object... arg) {
		final String realMessage=message;
        message = getMessage(message, 4, arg);        
        error(getSoaErrorLogger(),realMessage, message, arg);
    }

咋一看没有任何问题.可是上线后呈现第二个办法递归调用自身(可是第二个办法没有变更内容哈).本质上的原因便是由于修正第一个办法增加了入参.可是仅修正了第三个办法,第二个办法没有修正.没有呈现编译问题.由于自身第二个办法是一个Object… arg的数组调用.好坑.

四.总结

  • 区别ErrorException
  • 尽量不运用,少运用数组式运用.如String… args.Integer… args .即便要用,也尽量不要用Object… args .防止调用错误.
  • 在做技能优化时,尽可能评价影响,对线上抱有充沛的敬畏.慎之又慎.如没有特别的收益,可不上线.上线也要保证每一行改动与本次受影响的代码做到测验
  • 修正代码找到所有find usage ,防止呈现错改,漏改.可以利用自带IDE的工具 做到.
记一次 stackoverflowerror 线上排查进程