SpringBoot应用使用AWS DynamoDB V2 SDK出现老年代内存居高不下问题
我们有一个基于SpringBoot的应用,通过AWS DynamoDB V2 SDK的DynamoDBEnhancedClient对接DynamoDB表。部署到EKS并执行负载测试后,请求量较大导致内存占用如预期升高(内存占用上升图表)。但负载测试在图表的1:09停止后,内存未按预期回收,持续维持高位,即使没有新请求进入(内存高位维持图表)。
应用配置与实现
DynamoDB客户端配置类
@Configuration @RequiredArgsConstructor @Getter public class DynamoConfiguration { @Bean @Profile({"!local & !integrationtest"}) public DynamoDbClient getDynamoDbClient() { return DynamoDbClient.builder() .region(Region.of(System.getenv("AWS_REGION"))) .credentialsProvider(WebIdentityTokenFileCredentialsProvider.builder().build()).build(); } /** * Instantiates an enhanced DynamoDB client for interacting with DynamoDB. */ @Bean public DynamoDbEnhancedClient getDynamoDbEnhancedClient(DynamoDbClient dynamoDbClient) { return DynamoDbEnhancedClient.builder() .dynamoDbClient(dynamoDbClient) .build(); } }
Student表Repository实现(读密集型场景,负载测试以findById为主)
@Repository @RequiredArgsConstructor public class StudentRepository { private final DynamoDbEnhancedClient dynamoDbEnhancedClient; @Value("${dynamodb.tables.student}") private String studentTableName; /** * Retrieves a student by id. */ public Optional<Student> findById(final String id) { DynamoDbTable<Student> studentTable = getTable(); Key idKey = Key.builder().partitionValue(id).build(); return Optional.ofNullable(studentTable.getItem(idKey)); } /** * Saves a student to the table. */ public void save(final Student Student) { DynamoDbTable<Student> studentTable = getTable(); studentTable.putItem(Student); } /** * Gets Student table object. */ private DynamoDbTable<Student> getTable() { return dynamoDbEnhancedClient.table(studentTableName, TableSchema.fromBean(Student.class)); } }
疑问
- 现有配置(尤其是
@Bean和@Repository注解的使用)是否存在问题? - 是否需要主动终止数据库连接或销毁表对象来确保请求结束后内存回收?
- 是否需要改用EnhancedAsync客户端解决阻塞客户端的线程问题?
SDK版本依赖
<dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>dynamodb-enhanced</artifactId> <version>${aws.java.sdk.version}</version> </dependency> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>sts</artifactId> <version>${aws.java.sdk.version}</version> </dependency> <aws.java.sdk.version>2.19.18</aws.java.sdk.version>
堆转储信息更新
- 按保留大小排序的堆转储支配者:[堆转储支配者图表]
- 按保留大小排序的堆转储对象:[堆转储对象图表]
优化建议与排查方向
复用
DynamoDbTable对象
当前每次请求都会通过getTable()创建新的DynamoDbTable实例,而TableSchema.fromBean()会生成序列化/反序列化元数据,频繁创建会导致内存积累大量重复元数据对象。建议在Repository中初始化一次复用:@Repository public class StudentRepository { private final DynamoDbTable<Student> studentTable; public StudentRepository(DynamoDbEnhancedClient dynamoDbEnhancedClient, @Value("${dynamodb.tables.student}") String studentTableName) { this.studentTable = dynamoDbEnhancedClient.table(studentTableName, TableSchema.fromBean(Student.class)); } // 后续方法直接复用studentTable public Optional<Student> findById(final String id) { Key idKey = Key.builder().partitionValue(id).build(); return Optional.ofNullable(studentTable.getItem(idKey)); } }显式配置HTTP连接池参数
DynamoDbClient默认的HTTP客户端连接池可能在负载测试后保留大量空闲连接未释放。可以显式调整连接池参数:@Bean @Profile({"!local & !integrationtest"}) public DynamoDbClient getDynamoDbClient() { return DynamoDbClient.builder() .region(Region.of(System.getenv("AWS_REGION"))) .credentialsProvider(WebIdentityTokenFileCredentialsProvider.builder().build()) .httpClientBuilder(UrlConnectionHttpClient.builder() .maxConnections(50) // 根据场景调整 .connectionTimeout(Duration.ofSeconds(10)) .socketTimeout(Duration.ofSeconds(30)) .connectionMaxIdleTime(Duration.ofMinutes(1))) // 空闲连接超时回收 .build(); }升级SDK版本
当前使用的2.19.18版本较旧,可能存在已知内存泄漏问题。AWS SDK持续修复这类问题,建议升级到最新稳定版本(如2.20+),查看Release Notes确认是否有相关修复。深入分析堆转储细节
从堆转储图表重点排查:software.amazon.awssdk相关类是否持有大量对象引用- 是否有大量
Student对象或请求上下文未被回收(比如线程局部变量泄漏) - 线程池是否存在大量空闲核心线程未销毁,导致线程持有的内存无法释放
验证异步客户端的线程优化效果
同步阻塞客户端在高负载下会创建大量线程处理请求,核心线程池如果配置过高,负载结束后核心线程会持续存活占用内存。可以尝试改用EnhancedAsyncClient,异步模式下线程资源利用率更高,避免大量阻塞线程占用内存。确认Spring Bean生命周期配置
当前DynamoDbClient和DynamoDbEnhancedClient的单例Bean配置是正确的,Spring会管理其生命周期,不需要额外手动销毁。只需确保没有其他地方错误创建多个客户端实例。
内容的提问来源于stack exchange,提问作者Ace McCloud

