1. 内存调优
JVM 堆内存分为一块较大的 Eden 和两块较小的 Survivor。每次只使用 Eden 和其中一块 Survivor。当回收时 将 Eden 和 Survivor 中还存活IDE对象复制到 另外一块 Survivor 上,最后清理掉 Eden 和刚才使用过的 Survivor。也就是说当task创建出来对象会首先往 Eden 和 Survivor1 中存放,Survivor2 是空闲的,当 Eden 和Survivor1 区域放满以后就会触发 minor gc 小型垃圾回收,清理掉不再使用的对象。会将存活下来的对象放入Survivor2 中。
如果存活下来的对象大小大于 Survivor2 的大小,那么JVM就会将多余的对象直接放入到老年代中。
如果这个时候年轻代的内存不是很大的话,就会经常的进行 minor gc,频繁的minor gc会导致短时间内有些存活的对象(多次垃圾回收都没有回收掉,一直在用的又不能被释放,这种对象每经过一次minor gc都存活下来)频繁的倒来倒去,会导致这些短生命周期的对象(不一定长期使用)每进行一次垃圾回收就会长一岁。年龄过大,默认15岁,垃圾回收还是没有回收回去就会跑到老年代里面去了。
这样会导致在老年代中存放大量的短生命周期的对象,老年代应该存放的是数量比较少并且会长期使用的对象,比如数据库连接池对象。这样的话,老年代就会满溢(full gc 因为本来老年代中的对象很少,很少进行full gc 因此采取了不太复杂但是消耗性能和时间的垃圾回收算法)。不管minor gc 还是 full gc都会导致JVM的工作线程停止。
总结-堆内存不足造成的影响:
- 频繁的 minor GC。
- 老年代中大量的短生命周期的对象,导致 full GC。
- GC 多了会影响 Spark的性能和运行的速度。
Spark JVM 调优主要是降低 GC 时间,可以修改 Executor 内存比例参数。
RDD 缓存、task 定义的运行的算子函数,可能会创建很多对象,这样会占用大量的堆内存。对内存满了之后会频繁的 GC。如果 GC 还不能满足内存的需要的话就会报 OOM。
比如一个 task 在运行的时候创建 N 个对象,这些对象首先要放入到 JVM 年轻带中。
比如在存数据的时候我们使用了 foreach 来讲数据写入到内存,每条数据都会封装到一个对象存入数据库中,那么有多少条数据就会在 JVM 中创建多少对象。
2. Spark 中如何内存调优?
Spark Executor 堆内存中存放(以静态内存管理为例):
-
RDD 的缓存数据和广播变量:
spark.storage.memoryFraction 0.6
-
shuffle 聚合内存:
spark.shuffle.memoryFraction 0.2
-
task 的运行内存:0.2
那么如何调优呢?
- 提高 Executor 总体内存的大小。
- 降低存储内存比例或者降低聚合内存比例。
3. 如何查看 GC?
Spark WEBUI 中 job -> stage -> task。