场景

  • 在EC2上跑了一个归档程序
  • 归档程序从S3上拉取小文件,然后按照数据的key进行划分,存储到新文件中
  • 当文件数超过300W时,CPU占用超过70%,大概率超过80%
  • 查看jvisualvm后发现,大量CPU耗费在GC上,并且GC频率特别频繁
  • 新生代GC与老年代GC的触发频率达到 1:10
  • 老年代几乎被塞满

    追踪步骤

  • 先去dump了内存快照

  • 然后使用MAT的内存泄漏分析,找到了占空间最大的对象: com.amazonaws.services.s3.model.ListObjectsV2Result
  • 进一步追踪代码,发现程序在一开始通过循环调用ListObjects,一次性获取了300W文件的信息
  • 而且在获取了ListObjectsV2Result后,将result加入list,然后把list作为数据源传入线程池,list在线程池中并未被remove过,全程一直引用这所有的Result对象
  • 大量Result对象占据了内存
  • 并且Result对象中除了线程池需要的数据,还有一堆请求响应状态,白白占用空间

    优化方案

  • 放弃list,改用阻塞队列。不再一次性获取所有文件信息,而是等线程池消耗一批后,再获取下一批文件信息

  • 线程池处理完一批数据后,就会释放掉,不再引用
  • 队列中不再存放原始的Result对象,改为自己封装的更轻量的Task对象

    优化结果

  • 归档时间从两天也跑不完,到半个小时就可以跑完

  • CPU占用从70~80%,降到30%