
java 8 的 stream 操作导致 线程卡死
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([email protected]/ReferencePipeline.java:176) at java.util.ArrayList$ArrayListSpliterator.forEachRemaining([email protected]/ArrayList.java:1655) at java.util.stream.AbstractPipeline.copyInto([email protected]/AbstractPipeline.java:484) at java.util.stream.AbstractPipeline.wrapAndCopyInto([email protected]/AbstractPipeline.java:474) at java.util.stream.ReduceOps$ReduceOp.evaluateSequential([email protected]/ReduceOps.java:913) at java.util.stream.AbstractPipeline.evaluate([email protected]/AbstractPipeline.java:234) at java.util.stream.ReferencePipeline.collect([email protected]/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 @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 杀死重启了。
1 zhangleshiye 2022-08-01 17:15:32 +08:00 for 加 stream 看着有点吓人 |
2 DonaldY 2022-08-01 17:25:12 +08:00 估计是 OOM ,跟 Java8 stream 没啥关系。 这个在递归调诶。 allTableNames.addAll(getForeignKeyTable(childTables, info, foreignKeyInfos)); 可以去看下 gc.log 或者 日志中是否有 OutOfMemoryError |
3 Bootis 2022-08-01 17:34:01 +08:00 childTables 逻辑有问题,你可以加一个日志打印,应该第二次以后的每次递归调用的入参 tableNames 都是一样的 |
4 MarkP 2022-08-01 17:35:11 +08:00 你这个递归都没出口。。。 |
5 MarkP 2022-08-01 17:36:34 +08:00 看错了,有出口,但我怀疑就是这个递归的问题 |
6 hhjswf 2022-08-01 18:16:16 +08:00 恐怖。。这么多遍历,肉眼看上去起码有三层,再递归一下,这算法复杂度得是什么规模啊 |
7 coderstory OP @MarkP childTables 是空的 就返回了 |
8 coderstory OP 面向业务编程的结果 按代码一行行看很容易 就是查询一张表的外键以及外键表的外键表。。。整个外键引用链表全查出来 先循环表 在循环列 然后 表的列查询是否有外键 |
9 guxingke 2022-08-01 18:52:10 +08:00 递归了就有问题吧 a -> b -> c -> ... -> b -> ... |
10 DT37 2022-08-01 21:14:48 +08:00 我看 Stream 就头疼,用的太多就很懵逼 |
11 Hug125 2022-08-01 21:22:09 +08:00 via iPhone stream 用不明白的话建议先把 stream 换成 for debug 明白了再换回 stream 回归测试。 stream 和 for 混着来建议统一换成 stream 流在处理大批量的数据还是有性能优势的 |
12 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 ? 说实话代码这样我一般都懒得看具体逻辑 |
13 iosyyy 2022-08-01 21:57:28 +08:00 这个应该是拷贝 list 的时候太大导致卡住了 要不用个 map? 这过滤写的..不忍直视 |
14 itechify PRO/div> 2022-08-01 22:15:19 +08:00 via Android 这和 stream 没关系,改成 for 递归也一样 |
15 itechify PRO @oneisall8955 口误,for 迭代变量 |
16 itechify PRO @oneisall8955 遍历。。。 |
17 ChicC 2022-08-01 23:23:48 +08:00 没注释,已经理不清了 |
18 dqzcwxb 2022-08-02 00:57:46 +08:00 换成 for 一样卡死,跟 stream 没有关系 |
19 Vegetable 2022-08-02 01:14:39 +08:00 这写法麻了,这种复杂度还敢用 stream ?真的绝了 |
20 TWorldIsNButThis 2022-08-02 02:28:41 +08:00 via iPhone @Vegetable 他想干的事情根本不复杂,是瞎 jb 写的代码导致看起来复杂 |
21 TWorldIsNButThis 2022-08-02 02:32:43 +08:00 via iPhone @Vegetable 而且这里的所谓 stream 全 tm 是单步操作然后就 collect ,看得出来这人根本就不怎么会,完全是把 stream 当成 collectionutil.filter 在用 |
22 Aloento 2022-08-02 02:33:59 +08:00 好恐怖呀哈哈哈 |
23 chengchen 2022-08-02 03:38:51 +08:00 via iPhone 这不就是二叉树层序遍历的变形题吗,leetcode 的 easy 难度 |
24 MoYi123 2022-08-02 10:13:56 +08:00 看起来像是数据里有环. |
25 Belmode 2022-08-02 10:54:52 +08:00 数据库里存在表外键循环依赖了,导致内存居高不下,一直 GC |
27 zmal 2022-08-02 11:21:33 +08:00 线程卡死本身和 stream 没啥关系。 但这个代码写的实在是太辣了。stream 不是让这么用的。 |
28 lmshl 2022-08-02 11:32:05 +08:00 |
29 lmshl 2022-08-02 11:36:18 +08:00 |
32 bigfei 2022-08-02 18:15:08 +08:00 MYSQL 有元数据表的呀。。直接用 CTP 查询元数据表就可以了 |
33 bigfei 2022-08-02 18:17:08 +08:00 |
34 bigfei 2022-08-02 18:21:27 +08:00 |
35 lmshl 2022-08-02 18:49:20 +08:00 |
36 lmshl 2022-08-02 18:58:22 +08:00 |
37 nbndco 2022-08-02 19:00:08 +08:00 via iPad 每当这个时候我就特别能理解为什么说千万不要用新特性,没事不要修改不要更新不要升级了。这水平要是写 for 可能这代码还跑的快一点,至少不用 collect 这么多次。可读性本来就没有,所以也无所谓了。 |
40 golangLover 2022-08-13 01:02:48 +08:00 @lmshl new dark? 找不到啊 |
41 ozipin 2022-08-15 15:05:31 +08:00 是不是多表之间的外键形成了环状结构然后有没有加以检测 |