Java 8 的 stream 常规操作导致线程卡死

2022-08-01 17:06:01 +08:00
 coderstory

java 8 的 stream 操作导致 线程卡死

先贴一段堆栈打印 0x46 这个线程一直无法完成任务


root@data-f7b697db9-hq9lf:/app# jstack 8 | grep -A20 0x46
"http-nio-8200-exec-1" #60 daemon prio=5 os_prio=0 cpu=573228.20ms elapsed=783.76s tid=0x00007f8751e8d800 nid=0x46 runnable  [0x00007f871eaf1000]
   java.lang.Thread.State: RUNNABLE
        at java.util.stream.ReferencePipeline$2$1.accept(java.base@11.0.12/ReferencePipeline.java:176)
        at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(java.base@11.0.12/ArrayList.java:1655)
        at java.util.stream.AbstractPipeline.copyInto(java.base@11.0.12/AbstractPipeline.java:484)
        at java.util.stream.AbstractPipeline.wrapAndCopyInto(java.base@11.0.12/AbstractPipeline.java:474)
        at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(java.base@11.0.12/ReduceOps.java:913)
        at java.util.stream.AbstractPipeline.evaluate(java.base@11.0.12/AbstractPipeline.java:234)
        at java.util.stream.ReferencePipeline.collect(java.base@11.0.12/ReferencePipeline.java:578)
        at cn.bobmao.pro.data.repository.externalDataSourceHelper.DataBaseRepositoryImplCommon.getForeignKeyTable(DataBaseRepositoryImplCommon.java:32)
        at cn.bobmao.pro.data.repository.externalDataSourceHelper.DataBaseRepositoryImplCommon.getForeignKeyTable(DataBaseRepositoryImplCommon.java:40)
        at cn.bobmao.pro.data.repository.externalDataSourceHelper.DataBaseRepositoryImplCommon.getForeignKeyTable(DataBaseRepositoryImplCommon.java:40)
        at cn.bobmao.pro.data.repository.externalDataSourceHelper.DataBaseRepositoryImpl.getTableNames(DataBaseRepositoryImpl.java:66)
        at cn.bobmao.pro.data.repository.externalDataSourceHelper.ExternalDataSourceExecutor.getTableNames(ExternalDataSourceExecutor.java:57)
        at cn.bobmao.pro.data.service.ExternalDataSourceService.updateTable(ExternalDataSourceService.java:215)
        at cn.bobmao.pro.data.controller.ExternalDataSourceController.getTableInfo(ExternalDataSourceController.java:50)
        at cn.bobmao.pro.data.controller.ExternalDataSourceController$$FastClassBySpringCGLIB$$f577fbc0.invoke(<generated>)
        at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)

pid 为 70 的线程( 16 进制就是 0x46 )为异常线程

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                                                                                                                              
     12 root      20   0 7793308   1.9g  24564 S  61.3   6.0   2:03.58 G1 Conc#0                                                                                                                                                            
     70 root      20   0 7793308   1.9g  24564 S  16.3   6.0  10:17.51 http-nio-8200-e                                                                                                                                                      
     29 root      20   0 7793308   1.9g  24564 S   5.0   6.0   0:06.73 GC Thread#1                                                                                                                                                          
     10 root      20   0 7793308   1.9g  24564 S   4.7   6.0   0:06.69 GC Thread#0                                                                                                                                                          
     15 root      20   0 7793308   1.9g  24564 S   0.7   6.0   0:01.93 VM Thread  

对应 java 代码

    @Override
   public List<String> getForeignKeyTable(List<String> tableNames, DataSourceEntity info, List<ForeignKeyInfo> foreignKeyInfos) {
       List<ColumnInfo> result = new ArrayList<>();
       List<String> allTableNames = new ArrayList<>(tableNames);
       for (String tableName : tableNames) {
           result.addAll(findAllTable(info.getDataBaseName(), tableName));
       }
       result = result.stream().filter(column -> Arrays.stream(DB_KEYWORD).noneMatch(it -> column.getColumnName().equalsIgnoreCase(it))).collect(Collectors.toList());
       Map<String, List<ColumnInfo>> tables = result.stream().collect(Collectors.groupingBy(ColumnInfo::getTableName));
       List<String> childTables = new ArrayList<>();
       for (String name : tables.keySet()) {
           List<ColumnInfo> columnInfos = tables.get(name);
           for (ColumnInfo columnInfo : columnInfos) {
               List<ForeignKeyInfo> collect = foreignKeyInfos.stream().filter(it -> it.getSourceTableName().equals(name) && it.getSourceColumnName().equals(columnInfo.getColumnName())).collect(Collectors.toList()); // 32 行
               if (!CollectionUtils.isEmpty(collect)) {
                   //获取子表的表名
                   childTables.addAll(collect.stream().map(ForeignKeyInfo::getTargetTableName).collect(Collectors.toList()));
               }
           }
       }
       if (!CollectionUtils.isEmpty(childTables)) {
           allTableNames.addAll(getForeignKeyTable(childTables, info, foreignKeyInfos)); // 40 行
       }
       return allTableNames;
   }

一脸疑问,不清楚怎么排查。。。等一会儿容器就被 k8s 杀死重启了。

5847 次点击
所在节点    Java
41 条回复
zhangleshiye
2022-08-01 17:15:32 +08:00
for 加 stream 看着有点吓人
DonaldY
2022-08-01 17:25:12 +08:00
估计是 OOM ,跟 Java8 stream 没啥关系。

这个在递归调诶。
allTableNames.addAll(getForeignKeyTable(childTables, info, foreignKeyInfos));

可以去看下 gc.log 或者 日志中是否有 OutOfMemoryError
Bootis
2022-08-01 17:34:01 +08:00
childTables 逻辑有问题,你可以加一个日志打印,应该第二次以后的每次递归调用的入参 tableNames 都是一样的
MarkP
2022-08-01 17:35:11 +08:00
你这个递归都没出口。。。
MarkP
2022-08-01 17:36:34 +08:00
看错了,有出口,但我怀疑就是这个递归的问题
hhjswf
2022-08-01 18:16:16 +08:00
恐怖。。这么多遍历,肉眼看上去起码有三层,再递归一下,这算法复杂度得是什么规模啊
coderstory
2022-08-01 18:38:36 +08:00
@MarkP childTables 是空的 就返回了
coderstory
2022-08-01 18:40:46 +08:00
面向业务编程的结果 按代码一行行看很容易 就是查询一张表的外键以及外键表的外键表。。。整个外键引用链表全查出来 先循环表 在循环列 然后 表的列查询是否有外键
guxingke
2022-08-01 18:52:10 +08:00
递归了就有问题吧

a -> b -> c -> ... -> b -> ...
DT37
2022-08-01 21:14:48 +08:00
我看 Stream 就头疼,用的太多就很懵逼
Hug125
2022-08-01 21:22:09 +08:00
stream 用不明白的话建议先把 stream 换成 for debug 明白了再换回 stream 回归测试。
stream 和 for 混着来建议统一换成 stream
流在处理大批量的数据还是有性能优势的
Leviathann
2022-08-01 21:46:48 +08:00
评价一下:
为什么 columnInfo 的 list 要叫 result ?
为什么 result 要用 new list + foreach addAll 的方法初始化,然后又用 stream 过滤?
为什么过滤以后的 result 又直接赋值给 result ?
为什么复杂的 filter 不抽成函数?
为什么不用 map.entrySet().stream 遍历而是写得这么麻烦?
为什么要 foreignKeyInfos 过滤以后要 collect 再判空再 add 到 childTables 而不是直接 forEach 里 add?
为什么 foreignKeyInfos 过滤后的名字叫 collect ?
为什么不是遍历 foreignkeyinfos 而是遍历用来过滤的中间变量 tables ?

说实话代码这样我一般都懒得看具体逻辑
iosyyy
2022-08-01 21:57:28 +08:00
这个应该是拷贝 list 的时候太大导致卡住了 要不用个 map? 这过滤写的..不忍直视
oneisall8955
2022-08-01 22:15:19 +08:00
这和 stream 没关系,改成 for 递归也一样
oneisall8955
2022-08-01 22:16:24 +08:00
@oneisall8955 口误,for 迭代变量
oneisall8955
2022-08-01 22:16:34 +08:00
@oneisall8955 遍历。。。
ChicC
2022-08-01 23:23:48 +08:00
没注释,已经理不清了
dqzcwxb
2022-08-02 00:57:46 +08:00
换成 for 一样卡死,跟 stream 没有关系
Vegetable
2022-08-02 01:14:39 +08:00
这写法麻了,这种复杂度还敢用 stream ?真的绝了
TWorldIsNButThis
2022-08-02 02:28:41 +08:00
@Vegetable 他想干的事情根本不复杂,是瞎 jb 写的代码导致看起来复杂

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/870012

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX