美文网首页
StackOverflowError问题排查

StackOverflowError问题排查

作者: c7d122ec46c0 | 来源:发表于2022-04-04 23:17 被阅读0次

    同事跟我说线上的一个dubbo provider服务启动不了了,然后发了一段报错信息,因为这个项目之前一直是我在跟,我就登上机器看了下

    1.排查原因

    // 这边省略了很多,loadClass递归了导致了StackOverflowError
    Exception in thread "main" java.lang.StackOverflowError
            at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
        at java.lang.ClassLoader.defineClass1(Native Method)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
        at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
        at java.net.URLClassLoader.defineClass(URLClassLoader.java:468)
        at java.net.URLClassLoader.access$100(URLClassLoader.java:74)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:369)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:363)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:362)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)</font>
        at java.lang.ClassLoader.defineClass1(Native Method)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
        at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
        at java.net.URLClassLoader.defineClass(URLClassLoader.java:468)
        at java.net.URLClassLoader.access$100(URLClassLoader.java:74)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:369)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:363)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:362)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
        at java.lang.Class.getDeclaredMethods0(Native Method)
        at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
        at java.lang.Class.getDeclaredMethods(Class.java:1975)
        at org.springframework.util.ReflectionUtils.getDeclaredMethods(ReflectionUtils.java:612)
        at org.springframework.util.ReflectionUtils.doWithMethods(ReflectionUtils.java:524)
        at org.springframework.util.ReflectionUtils.doWithMethods(ReflectionUtils.java:510)
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.determineCandidateConstructors(AutowiredAnnotationBeanPostProcessor.java:241)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.determineConstructorsFromBeanPostProcessors(AbstractAutowireCapableBeanFactory.java:1069)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1042)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:510)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482)
        at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:772)
        at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:839)
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:538)
        at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
        at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:93)
        at com.alibaba.dubbo.container.spring.SpringContainer.start(SpringContainer.java:50)
        at com.alibaba.dubbo.container.Main.main(Main.java:80)
    
    

    整个异常的调用栈就是这样。初步看起来是spring容器在创建bean的时候,加载这个bean相关的类时导致了栈溢出。
    我们部署的dubbo provider使用的是 Dubbo 中的服务容器,dubbo的服务容器启动使用的是下图标红的start.sh


    1648997410(1).png

    这个脚本中的设定是64位机器上线程的栈最大是256k,虽然256k的确有些小,但之前一直也都ok的,怎么现在不行了。

    BITS=`java -version 2>&1 | grep -i 64-bit`
    if [ -n "$BITS" ]; then
        # JAVA_MEM_OPTS=" -server -Xmx2g -Xms2g -Xmn256m -XX:PermSize=128m -Xss256k -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 "
        JAVA_MEM_OPTS=" -server -Xmx2g -Xms2g -Xmn256m -XX:MetaspaceSize=128m -Xss256k -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 "
    else
        JAVA_MEM_OPTS=" -server -Xms1g -Xmx1g -XX:PermSize=128m -XX:SurvivorRatio=2 -XX:+UseParallelGC "
    fi
    

    本地启动vm参数加入-Xss256k 果然是相同的报错,加大到-Xss512k就能正常启动了。但我还是想看看到底是什么新的代码导致了这个问题。
    分析调用栈是spring容器初始化bean的时候需要获取对象的构造方法,遍历了这个类的所有的方法,期间触发了类的加载,导致的stackOverflow。可是这个代码并没有日志打印beanName,无从知晓是哪个类引起的问题,也不可能在这边打断点一个个看,毕竟那么多需要加载的bean呢,那有什么办法呢。javaAgent加日志好像可行。

        @Override
        public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName) throws BeansException {
            if (!this.lookupMethodsChecked.contains(beanName)) {
                ReflectionUtils.doWithMethods(beanClass, new ReflectionUtils.MethodCallback() {
                    @Override
                    public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
                        Lookup lookup = method.getAnnotation(Lookup.class);
                        if (lookup != null) {
                            LookupOverride override = new LookupOverride(method, lookup.value());
                            try {
                                RootBeanDefinition mbd = (RootBeanDefinition) beanFactory.getMergedBeanDefinition(beanName);
                                mbd.getMethodOverrides().addOverride(override);
                            }
                            catch (NoSuchBeanDefinitionException ex) {
                                throw new BeanCreationException(beanName,
                                        "Cannot apply @Lookup to beans without corresponding bean definition");
                            }
                        }
                    }
                });
                this.lookupMethodsChecked.add(beanName);
            }
    
    

    2.写一个javaagent

    参考了如下博文
    基于 Javassist 和 Javaagent 实现动态切面
    JVM插码之三:javaagent介绍及javassist介绍
    使用agent和javassist实现基于jvm的aop日志打印系统
    完成了自己的实现,具体的实现可以查看github 这边就不具体讲了
    传入类名和方法名打印指定方法的入参
    jvm 参数加上

     -javaagent:D:\javaAgentDemo\javaAgent\target\javaAgent-1.0.0-SNAPSHOT.jar=AutowiredAnnotationBeanPostProcessor,determineCandidateConstructors
    

    日志打印显示是我们使用obs的bean,每次启动到这就会打印StackOverflowError


    image.png

    锁定了一个方法,为什么锁定这个方法呢
    最近我们的对象存储服务从阿里云迁移到了华为云,其他方法都跟阿里云一样,就这个是我新增的一个方法。

    public ObsClient getObsClient() {
            String accessKeyId = obsConf.getAccessKeyId();
            String accessKeySecret = obsConf.getAccessKeySecret();
            String endpoint = obsConf.getEndpoint();
            return new ObsClient(accessKeyId, accessKeySecret, endpoint);
        }
    

    可看起来这个方法也没什么特殊的啊,一看这个方法的返回吓一跳。ObsClient 的继承关系居然有20多层,spring获取ObsOperateServiceImpl的方法时需要加载ObsClient这个类,由于这个类的继承层级有20多层导致了StackOverflowError。把这个方法注释掉就能正常启动了。
    (华为云的代码真心不怎么样啊,这个ObsClient集合了一堆东西,jar包就有5m大)


    image.png

    可惜的是google了很久也没有找到能准确统计栈内存增加的方法。

    相关文章

      网友评论

          本文标题:StackOverflowError问题排查

          本文链接:https://www.haomeiwen.com/subject/nlyqsrtx.html