起因
朋友有一个大的文件,需要把里面的格式做转换后重新生成一个新的文件,然后用matlab做一些数据处理,但是说做这个数据格式转换的时候过程十分慢,而且跑了好几个小时都没搞好,我一看文件大小300m,就自信满满的说我来帮你搞定。
过程
拿到文件之后格式是这样的
我看了一下这个数据大概将近5百万条,都需要转换成这种格式
我以为数据大小没有关系,就直接跑了一个单线程做所有的事情,一行一行读出来,转成对象后,然后把对象一个一个拿出来,转成最后的格式输出。
开始跑了之后,发现跑了好久都没有出来,打了个断点,发现跑了十几分钟后跑了一半的数据,心有不甘啊,太慢了。然后开始修改代码
既然太慢了,搞多线程干
直接把转换的过程封装到方法里面,新建了一个线程池
private ThreadPoolExecutor executor = new ThreadPoolExecutor(20,30,60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2000),new ThreadPoolExecutor.CallerRunsPolicy());
重新开始跑,发现跑到一半任务左右又变得很慢
思考
- 队列被塞满了,因为我的拒绝策略是
CallerRunsPolicy
- 5百万数据的话,是不是一下子就把队列给塞满了
- 应该任务的处理时间内提交可以处理完成的任务数量
所以又改了一下
- 把阻塞队列容量加大到
20000
- 提交任务的时候每
30
个任务主线程sleep(200)
,减慢任务提交速度
改完之后跑还是同样的问题,比较疑惑,打开资源管理器观察,内存占用%80,cpu占用100%
思考
- 内存还有剩余,cpu100%,说明有线程一直占用着cpu,但是为什么还是很慢的速度呢,看来不是工作线程占用这cpu,(其实这个时候应该想到的是垃圾回收线程占用cpu)
- 关闭程序之后内存直接降到了40%
- 可能是堆内存不够了,导致一直fullGC,一直没有多余的内存空间去分配新的空间创建新的对象,所以导致很慢
基于上面的思考做了改进
- 吧idea的vm运行时参数改了一下,改成
-Xmx2048m -Xms2048m
,全部改成了两个G,
跑了一下发现还是同样的问题,虽然进度稍微多了一点,可以感觉到是堆内存的问题了,接下来的改进也就可以知道了,不能把全部的数据读取之后转为对象之后在进行转换,这样会造成堆空间不足,无法分配空间。改进:每50个异步任务进行一次阻塞,并将结果直接做转换后写入新的文件。修改之后整个过程耗时1分钟左右吧。
学习
这次的数据转换过程,其实一些理论自己都懂,但是没有运用到实际的写代码的过程,这次稍微有一点体会了。
- 多线程工作,数据量大的时候,不应该全部阻塞去等待结果,这样子会占用很多的内存空间
- 前面的思考其实有很多是错误的方向,就算任务塞满了,和你的处理工作是不相关的,因为拒绝策略是
CallerRunsPolicy()
,所以线程池的队列大小没有关系 - cpu占用100%的时候就应该想到去用Java提供的工具去看什么线程在占用cpu,因为任务一直被阻塞,所以肯定不是工作线程被阻塞了,肯定是垃圾回收线程在工作,jstat 看一下GC情况。
- 内存占用很多的时候,jstat 看一下jvm的内存空间情况。这时候修改vm的xms和xmx是没有用的,就像线上频繁gc的时候用加机器的方法来解决问题,不是最终解决方法
- 这种任务量很多的时候要考虑修改程序逻辑使一些对象可以及时释放,这样不会占用太多空间导致频繁full gc,降低cpu使用率