Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

java.lang.StackOverflowError 错误问题 #64

Open
zwc456baby opened this issue Mar 24, 2020 · 4 comments
Open

java.lang.StackOverflowError 错误问题 #64

zwc456baby opened this issue Mar 24, 2020 · 4 comments

Comments

@zwc456baby
Copy link

zwc456baby commented Mar 24, 2020

已经找到原因以及解决办法(由于个人fork 了此项目一份代码,并且改动很大,源码已经和 LogUtils 库不同了。所以不好修改,待我有空会提交并关闭此问题)
提出此问题望作者修复
谢谢。

复现代码

        // 先创建一个 map 和 message
        // 注意,map 和 message 都有 Parser 实现
        // 分别对应 MapParse 和 MessageParse
        Map<String, Object> testMap = new HashMap<>();
        Message message = Message.obtain();

        // 在这里,map 持有 message, 而 message 持有 map
        message.obj = testMap;
        testMap.put("testKey", message);

        LogUtil.objToString(testMap);

其中 LogUtil.objToString 方法是我调试用的方法
它实际是这样的

    public static String objToString(Object object) {
        return ObjectUtil.getInstance().objectToString(object);
    }

也就是说,ObjectUtil 的 objectToString 方法内部实现有一点小错误

分析及解决

原因就是循环引用后,解析的时候,循环解析了。
虽然平常确实不会有这种情况,但是实际确实遇到了

        viewModel?.helloWorld?.observe(this, Observer {
            //            LogUtil.objToString()
         默认情况下,解析 ObservableField 会死循环
            logger.i("listener on changed" + com.zhouzhou.newsdemo.LogUtil.objToString(it))
        })

分析:

以上面代码为例

当检测到 Object 是一个 Map 的时候,调用了 MapParse.parseString 方法
然后 MapParse 中 又调用了 ObjectUtil.getInstance().objectToString(value) 来解析 value
而此 value 就是 Message,而 Message 中又引用了 Map,这时候又去解析 Map。不断的循环

然后......就炸了

解决

在 ObjectUtil 类中,解析 Object 对象时,传递了 childLevel 层级。
所以,对 Object 对象的循环引用不会导致死循环。
但是对继承Parser的解析类,没有传递 childLevel 层级,导致解析时死循环

解决办法应该是:将 Parser 接口方法 String parseString(T t); 增加一个参数,childLevel 层级
在 Parser 实现类中,调用 ObjectUtil.getInstance().objectToString 方法时,将 childLevel 层级始终传递并 +1,使其可以被跳出,防止死循环了

不与此 bug 有关的一点优化建议

package com.apkfuns.logutils 中的 Logger

方法 getTopStackInfo 调用了两次 getCurrentStackTrace

    private String getTopStackInfo() {
        String customTag = mLogConfig.getFormatTag(getCurrentStackTrace());
        if (customTag != null) {
            return customTag;
        }
        StackTraceElement caller = getCurrentStackTrace();
        String stackTrace = caller.toString();
        stackTrace = stackTrace.substring(stackTrace.lastIndexOf('('), stackTrace.length());
        String tag = "%s.%s%s";
        String callerClazzName = caller.getClassName();
        callerClazzName = callerClazzName.substring(callerClazzName.lastIndexOf(".") + 1);
        tag = String.format(tag, callerClazzName, caller.getMethodName(), stackTrace);
        return tag;
    }

建议改成只调用一次 getCurrentStackTrace
因为 Thread.currentThread().getStackTrace() 方法是一个相对耗时操作。
之前测试过 性能。改成只调用一次之后,性能提升大约 百分之70
大概就是 一万条日志从 400ms 减少到 两百多ms

    private String getTopStackInfo() {
        StackTraceElement caller = getCurrentStackTrace();
        if (caller == null) return "Null Stack Trace";
        String stackTrace = caller.toString();
        stackTrace = stackTrace.substring(stackTrace.lastIndexOf('('));
        String tag = "%s.%s%s";
        String callerClazzName = caller.getClassName();
        callerClazzName = callerClazzName.substring(callerClazzName.lastIndexOf(".") + 1);
        tag = String.format(tag, callerClazzName, caller.getMethodName(), stackTrace);
        return tag;
    }

    private StackTraceElement getCurrentStackTrace() {
        StackTraceElement[] trace = Thread.currentThread().getStackTrace();
        int stackOffset = getStackOffset(trace);
        return stackOffset == -1 ? null : trace[stackOffset];
    }

    private int getStackOffset(StackTraceElement[] trace) {
        for (int i = tackOffset; i < trace.length; i++) {
            if (trace[i].getClassName().equals(findTackClassName)) return ++i;
        }
        return -1;
    }

@zwc456baby
Copy link
Author

修改后的打印日志:

I/LogUtils: java.util.HashMap [
    testKey -> android.os.Message [
    what = 0
    when = 0
    arg1 = 0
    arg2 = 0
    data = android.os.Bundle [
    ]
    obj = java.util.HashMap [
    testKey -> { when=-19d7h52m44s765ms barrier=0 }
    ]
    ]
    ]

已经没有循环解析导致的问题了,最后的

testKey -> { when=-19d7h52m44s765ms barrier=0 }

已经跳出了解析。

@pengwei1024
Copy link
Owner

棒棒棒!! 最近工作比较忙,周末会抽空看看的,感谢反馈

@zwc456baby
Copy link
Author

另外。Log4a 日志文件写入库,cpp 文件我提交过 bug 修复,Log4a 已经合并到 主分支了

pqpo/Log4a@6942b7a

建议您也从 Log4a 重新同步一下 cpp 文件

@pengwei1024
Copy link
Owner

9150e4e5gy1gbmmiwmht8j205k05kdfr

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants