Java读取大Parquet文件的内存溢出问题及相关疑问
问题描述
我正在编写Java程序逐行处理一个Parquet大文件,该文件包含约130万行、3000列双精度浮点数,文件大小约6.6G。按照教程实现读取代码时,运行抛出OutOfMemoryError,推测是row group加载了全部130万行导致内存不足。
之后用pandas重新保存该文件:
- 设置
row_group_size=1000时,打开文件直接触发数组长度过大的OutOfMemoryError - 设置
row_group_size=10000时,读取部分行后仍出现堆内存溢出,但移除simpleGroups.add(simpleGroup)这行代码后能完成读取(耗时10分28秒,而pandas读取仅需18.6秒)
疑问列表
- 机器有54G空闲内存,已设置VM参数
-Xmx30g和-Xms30g,为何row_group_size=10000时仍出现堆内存溢出? - 为何
row_group_size较小时会触发不同的错误? - 若需逐行处理数据,是否必须通过设置小
row_group_size避免预加载大量行占用内存?
问题解答
1. 为何row_group_size=10000时仍堆内存溢出?
10000行×3000列的双精度浮点数,单row group的原始数据量是10000×3000×8字节=240MB,但Java读取Parquet时的内存开销远不止这个数:
SimpleGroup对象本身有对象头、引用等额外开销,每列的数值如果用Double包装类(而非原生double)存储,内存占用直接翻倍;- 再加上Parquet读取过程中的中间缓存(列解码器的临时缓冲区、字典映射表等),以及JVM本身的元空间、GC预留内存,30G堆内存其实没那么充裕。尤其是你之前把每个
SimpleGroup都加到simpleGroups列表里,等于把整个row group的所有行都留在内存,多个row group叠加起来,很容易就把堆内存撑爆。
你移除simpleGroups.add(simpleGroup)后能跑完,也直接证明了是这些缓存的SimpleGroup实例占满了内存。
2. row_group_size较小时为何触发不同错误?
当row_group_size=1000时,每个row group的列数还是3000,Parquet的Java库在处理列数据时,可能出现两种情况:要么是代码逻辑错误(比如误把列数当成行数创建数组),要么是库本身对小row group的处理存在bug,导致创建了远超预期的大数组,进而触发“数组长度过大”的错误。而row group较大时,内存是被大量对象实例逐步占满,所以表现为普通的堆内存溢出。
3. 逐行处理是否必须设置小row_group_size?
不是必须,但设置合适的row_group_size能降低单次加载的内存压力。更核心的是不要把所有行都缓存到内存里:
- 读取每一行(或每一个
SimpleGroup)后,立即处理数据,处理完就丢弃该对象,别把它加到列表里长期持有; - 尽量使用Parquet的面向列的流式读取API,比如用
ParquetReader的read()方法逐行读取,而非一次性加载整个row group; - 优先用原生类型(比如
double[])存储列数据,避免使用Double包装类,减少额外内存开销。
如果原文件的row group过大,用pandas重新拆分时,选择50000左右的row_group_size(平衡内存和IO效率)会更合理,但核心始终是处理完就释放内存,不要缓存无用数据。
内容的提问来源于stack exchange,提问作者Harry Braviner
相关产品推荐
相关产品推荐

