Spring响应式与非响应式内存占用对比及技术疑问
Spring 阻塞/响应式模型内存占用疑问
背景调研与测试
调研得知Spring响应式因Tomcat线程非阻塞,堆内存消耗应较低。采用Spring Data JPA传统阻塞非响应式模型测试该假设,代码如下:
@GetMapping("/customers") public List<Customer> getAllCustomers() { long beforeUsedMem = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); List<Customer> customerList = customerRepository.findAll(); // Your operation here long afterUsedMem = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); System.out.println("Approximate memory used: " + (afterUsedMem - beforeUsedMem) + " bytes"); return customerList; }
查询数据库中200万条记录,测得customerList内存占用约1GB,输出:
Approximate memory used: 1371603632 bytes
技术疑问
customerList是否存储在堆内存中?查询200万条记录并存储到列表是否会引发OOM错误?若当前内存足够避免崩溃,在无闲置内存时是如何解决内存问题的?本次运行代码未报错。- 若采用如下响应式方式查询数据,内存占用是否会有优化?
@GetMapping("/") public Flux<Customer> getCustomers() { return customerRepository.findAll() .doOnNext(customer -> System.out.println(customer.getCustomer_name())); }
问题解答
针对疑问1的解答
customerList肯定存储在堆内存中:Java中所有对象实例(包括集合及其中的Customer对象)默认都分配在堆内存,除非使用了特殊堆外内存API,这里显然没有。- 查询200万条记录存入列表可能引发OOM:如果JVM堆内存不足以容纳这1GB左右的数据,就会抛出
OutOfMemoryError。你这次没报错,只是当前JVM堆内存预留空间足够放下这些数据。 - 无闲置内存时的处理逻辑:当堆内存不足时,JVM会先触发**垃圾回收(GC)**尝试释放不再使用的对象内存;如果GC后仍无足够空间分配新对象,才会抛出OOM错误。如果应用此时没有其他大对象占用内存,GC可能能腾出空间,但如果所有内存都被活跃对象占用(比如
customerList还在被引用),必然会触发OOM。
针对疑问2的解答
响应式方式的内存占用会有明显优化,核心原因是Flux采用流式处理模型:
- 传统阻塞模型中,
customerRepository.findAll()会一次性把200万条记录全部加载到内存的List里,所有Customer对象同时存在于堆中,导致内存占用飙升。 - 响应式模型中,
Flux<Customer>不会一次性加载所有数据,而是按需流式获取:数据库驱动会分批从数据库拉取数据(比如每次拉取几百条),处理完当前批次的Customer(比如执行doOnNext的打印逻辑)后,这些对象就会失去引用,等待GC回收,再拉取下一批。整个过程中,堆里只会保留当前正在处理的少量Customer对象,内存占用会大幅降低(可能从GB级降到MB级)。
需要注意:这要求Spring Data JPA响应式实现(比如Spring Data R2DBC)支持流式查询,底层数据库驱动也要支持非阻塞流式读取,否则可能还是会一次性加载全部数据,达不到内存优化的效果。
内容的提问来源于stack exchange,提问作者trilocho
相关产品推荐
相关产品推荐

