StackOverflowError问题排查

同事跟我说线上的一个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)
    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.(ClassPathXmlApplicationContext.java:139)
    at org.springframework.context.support.ClassPathXmlApplicationContext.(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日志打印系统
完成了自己的实现,具体的实现可以查看传入类名和方法名打印指定方法的入参agent实现--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问题排查)