[虚心请教] JDK8 默认的配置环境下,10G 的堆内存 Full GC 一次要 16s 左右,该如何优化?处理一个请求内存耗费大约 900M

2019-10-16 12:42:58 +08:00
 summer7
7134 次点击
所在节点    Java
63 条回复
semut
2019-10-16 19:28:17 +08:00
一次请求 30000 条数据,设计不太合理,可以简单说说这个任务的目的,看下有没有其他方案实现
phantomzz
2019-10-16 20:07:42 +08:00
长文本存 elasticsearch,其他 200 字段该拆表拆表,将 30000 条数据水平分割到不同 机器 /进程 流式处理,全部处理完毕再汇聚。
Jonz
2019-10-16 20:15:58 +08:00
关注下进展
uyhyygyug1234
2019-10-16 20:54:28 +08:00
@phantomzz 我感觉也是按照老哥的说法。

900MB 3w 条数据 每条数据 30k

这边 magic number 为啥要取 3w
3w 条之间本身是否有关系,可否分割,分布式处理
af463419014
2019-10-16 21:02:13 +08:00
用堆外内存

处理的数据单独放在堆外内存,用完手动释放,可以跟 JVM 的 GC 分开
JVM 堆内存就不需要 10G 这么大了
pangliang
2019-10-16 21:08:52 +08:00
3 万条数据为啥要一次性读入内存? 用了 orm 吧? 查完库只能返回一个 3 万的 list, 然后遍历 list?
直接 jdbc 里查, 完了用 jdbc 的 result.next 去循环直接处理, 不要拼到 list 再遍历;
这样就不会有 3 万行在内存了
如果还是不行, 检查下 jdbc 返回数据的方式; 可以设置, next 一次返回一行
php 都有, java 不可能没有
SoloCompany
2019-10-16 21:14:27 +08:00
加并发控制,限制 slow operation 的并发数,预留 30%以上的空闲 heap
mxalbert1996
2019-10-16 21:15:54 +08:00
@sadfQED2 换语言的意义在于减少 GC 的时间啊
summer7
2019-10-16 21:50:44 +08:00
@phantomzz 其实在之前用 hbase 存储是分表的,但是每个表也是 200+字段的,之后底层数据库弃用了 hbase,把所有表数据汇聚在一张超大表中,数据量相当的大。
感觉“合久必分”这句话太适合描述这种存储方式变化了
summer7
2019-10-16 21:53:30 +08:00
@summer7 合久必分,分久必合
summer7
2019-10-16 21:57:20 +08:00
@pangliang 感谢老哥的回复。源于对 JDBC 理解的不是很深,目前我的做法是 next 遍历完,30000 条数据存入 list 再交给具体的解析方法解析。 也有其他大佬和老哥你一样说,可以 next 遍历时一条一条解析,或许这个就是解决问题的办法吧,待我一试。
neoblackcap
2019-10-16 23:04:29 +08:00
为什么需要一次读 3W 条数据?流式处理嘛,你业务逻辑不改,换 JVM 也未必有成效。
毕竟假如全部都是新生代对象,但是在 GC 被触发的时候,这些对象很有可能还是活的,有其他业务代码在使用这它们。你这个整体并发一样上不去。
这个东西还是得结合你的业务逻辑进行分析才行
Buffer2Disk
2019-10-17 00:01:22 +08:00
改架构吧,这业务设计就不合理。。。
swulling
2019-10-17 01:24:13 +08:00
每次请求结束后都主动触发一次 GC 吧😄
v2orz
2019-10-17 08:31:32 +08:00
换语言和堆外内存方案建议别搞
换语言作用不大
堆外内存,既然你们都设计成这样了,想想堆外内存管理估计也很难做好,不建议去踩
一次少查点数据或者流式处理就差不多了,GC 换 G1
softtwilight
2019-10-17 09:01:13 +08:00
把 resultSet 包装为流,一条一条解析,十分省内存;
return StreamSupport.stream(new Spliterators.AbstractSpliterator<Record>(
Long.MAX_VALUE,Spliterator.ORDERED) {
@Override
public boolean tryAdvance(Consumer<? super Record> action) {
try {
if(!resultSet.next()) return false;
action.accept(createRecord(resultSet));
return true;
} catch(SQLException ex) {
throw new RuntimeException(ex);
}
}
}, false).onClose(() -> closeConnectionAnd...())
haochih
2019-10-17 09:20:16 +08:00
jdbc 默认情况下的读会把 sql 语句的结果全部加载到内存中,这种大数据量的读可以考虑采用 stream read。详情参见 https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-implementation-notes.html 中 ResultSet 一节。
tairan2006
2019-10-17 11:12:36 +08:00
这架构有问题,数据拆开,或者改成流处理
sorra
2019-10-17 12:10:09 +08:00
JDBC ResultSet 有 vendor 差异性,默认情况下:
- MySQL 和 PostgreSQL 会一次加载所有行到 JVM
- Oracle 每次只加载 10 行到 JVM,DB 这边维持一个会话和偏移量,JVM 可以不停地推进偏移量,直到读完所有行
这个行为可配置(ResultSet 可以 setFetchSize),详情见 vendor 的文档

如果 vendor 支持 setFetchSize,你可以流式处理数据,不要都堆到内存里才全部处理

如果 vendor 不支持 setFetchSize 和 LIMIT,也可以想想能不能在 SQL 语句上想想办法
sorra
2019-10-17 12:15:03 +08:00
Statement 也可以 setFetchSize,为了防止来不及,可以在 Statement 就设上

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

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

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

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

© 2021 V2EX