Vaadin 8 Grid加载时内存溢出异常问题求助
Vaadin 8 Grid 内存溢出问题排查与解决
问题现象
- 加载包含用户详情的Grid标签页时,应用因内存不足崩溃,2名以上用户同时访问时触发
- 核心报错:
Caused by: java.lang.OutOfMemoryError: GC overhead limit exceeded Caused by: org.postgresql.util.PSQLException: Ran out of memory retrieving query results. - 数据库查询速度快,但UI端加载缓慢
代码问题分析
- 全量数据加载:
setDataSource(List<User> users)将所有用户数据一次性加载到内存,多用户会话同时持有全量数据时,内存占用急剧飙升。 - 详情组件重复创建:
setDetailsGenerator中每次展开行详情都会新建UserDetailsView实例,大量重复实例持续占用内存。 - 转换器重复实例化:角色列渲染逻辑中,每次渲染都会新建
UsersViewRoleConverter,增加不必要的对象创建与GC压力。 - 导出功能内存风险:
Exporter.exportAsExcel(usersGrid)直接遍历Grid全量数据,导出时会瞬间占用大量内存。
解决方案
1. 实现分页懒加载(核心优化)
替换全量数据加载为DataProvider分页加载,每次仅获取当前页数据:
// 替换原有setDataSource方法 void setDataProvider(DataProvider<User, String> dataProvider) { usersGrid.setDataProvider(dataProvider); }
后端配合分页查询(如Spring Data JPA的Pageable),每次返回20-50条数据,大幅降低单会话内存占用。
2. 优化详情组件创建
避免重复创建详情组件,可复用或延迟初始化:
private void createUsersGrid() { // ... 其他代码 // 详情生成器可考虑轻量级实现,确保关闭时能被GC回收 usersGrid.setDetailsGenerator(rowRef -> { UserDetailsView detailsView = new UserDetailsView(listener); if (rowRef != null) { detailsView.edit(rowRef); } return detailsView; }); }
3. 减少重复对象创建
将UsersViewRoleConverter实例化为类成员变量,避免每次渲染列都新建对象:
private final UsersViewRoleConverter roleConverter = new UsersViewRoleConverter(); // 角色列渲染逻辑 usersGrid.addColumn(user -> roleConverter.convertToPresentation(user.getRoles(), new ValueContext()) ).setCaption(COLUMN_ROLES);
4. 优化导出功能
导出全量数据时,单独执行分页查询拼接结果,避免一次性加载全量数据到内存:
Button export = new Button("Export"); export.setIcon(VaadinIcons.LIST_UL); export.addStyleName("move-right"); StreamResource excelStreamResource = new StreamResource(() -> { // 后端分页获取全量用户数据 List<User> allUsers = listener.getAllUsersForExport(); // 指定导出字段,避免不必要的数据加载 return Exporter.exportAsExcel(allUsers, User.class, COLUMN_LAST_NAME, COLUMN_FIRST_NAME, COLUMN_USERNAME, COLUMN_EMAIL, COLUMN_ROLES, COLUMN_ENABLED); }, "Users Report.xlsx"); FileDownloader excelFileDownloader = new FileDownloader(excelStreamResource); excelFileDownloader.extend(export);
5. 数据库JDBC优化
调整PostgreSQL JDBC连接的fetchSize,避免一次性拉取所有查询结果到客户端内存:
// 在JDBC查询语句中设置fetch size statement.setFetchSize(50);
6. 额外检查点
- 检查
User对象是否包含大字段(如Blob、Clob),若存在则延迟加载,仅在详情展开时获取 - 用JProfiler等工具监控内存,确认是否存在未释放的Grid实例或User对象泄漏
内容的提问来源于stack exchange,提问作者Ciby
相关产品推荐
相关产品推荐

