StackOverflowError問題排查

同事跟我說線上的一個(gè)dubbo provider服務(wù)啟動(dòng)不了了,然后發(fā)了一段報(bào)錯(cuò)信息,因?yàn)檫@個(gè)項(xiàng)目之前一直是我在跟,我就登上機(jī)器看了下

1.排查原因

// 這邊省略了很多,loadClass遞歸了導(dǎo)致了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)

整個(gè)異常的調(diào)用棧就是這樣。初步看起來是spring容器在創(chuàng)建bean的時(shí)候,加載這個(gè)bean相關(guān)的類時(shí)導(dǎo)致了棧溢出。
我們部署的dubbo provider使用的是 Dubbo 中的服務(wù)容器,dubbo的服務(wù)容器啟動(dòng)使用的是下圖標(biāo)紅的start.sh


1648997410(1).png

這個(gè)腳本中的設(shè)定是64位機(jī)器上線程的棧最大是256k,雖然256k的確有些小,但之前一直也都o(jì)k的,怎么現(xiàn)在不行了。

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

本地啟動(dòng)vm參數(shù)加入-Xss256k 果然是相同的報(bào)錯(cuò),加大到-Xss512k就能正常啟動(dòng)了。但我還是想看看到底是什么新的代碼導(dǎo)致了這個(gè)問題。
分析調(diào)用棧是spring容器初始化bean的時(shí)候需要獲取對(duì)象的構(gòu)造方法,遍歷了這個(gè)類的所有的方法,期間觸發(fā)了類的加載,導(dǎo)致的stackOverflow。可是這個(gè)代碼并沒有日志打印beanName,無從知曉是哪個(gè)類引起的問題,也不可能在這邊打斷點(diǎn)一個(gè)個(gè)看,畢竟那么多需要加載的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.寫一個(gè)javaagent

參考了如下博文
基于 Javassist 和 Javaagent 實(shí)現(xiàn)動(dòng)態(tài)切面
JVM插碼之三:javaagent介紹及javassist介紹
使用agent和javassist實(shí)現(xiàn)基于jvm的aop日志打印系統(tǒng)
完成了自己的實(shí)現(xiàn),具體的實(shí)現(xiàn)可以查看傳入類名和方法名打印指定方法的入?yún)gent實(shí)現(xiàn)--GitHub這邊就不具體講了
jvm 參數(shù)加上

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

日志打印顯示是我們使用obs的bean,每次啟動(dòng)到這就會(huì)打印StackOverflowError


image.png

鎖定了一個(gè)方法,為什么鎖定這個(gè)方法呢
最近我們的對(duì)象存儲(chǔ)服務(wù)從阿里云遷移到了華為云,其他方法都跟阿里云一樣,就這個(gè)是我新增的一個(gè)方法。

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

可看起來這個(gè)方法也沒什么特殊的啊,一看這個(gè)方法的返回嚇一跳。ObsClient 的繼承關(guān)系居然有20多層,spring獲取ObsOperateServiceImpl的方法時(shí)需要加載ObsClient這個(gè)類,由于這個(gè)類的繼承層級(jí)有20多層導(dǎo)致了StackOverflowError。把這個(gè)方法注釋掉就能正常啟動(dòng)了。
(華為云的代碼真心不怎么樣啊,這個(gè)ObsClient集合了一堆東西,jar包就有5m大)


image.png

可惜的是google了很久也沒有找到能準(zhǔn)確統(tǒng)計(jì)棧內(nèi)存增加的方法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容