Spark - 性能优化详解3(JVM垃圾回收调优)
作者:hangge | 2024-07-29 08:37
三、JVM 垃圾回收调优
1,Java 中的 GC 介绍
(1)Java 堆空间被划分成了两块空间:一个是年轻代,一个是老年代。
- 年轻代放的是短时间存活的对象,占堆内存的 1/3。
- 老年代放的是长时间存活的对象,占堆内存的 2/3。
(2)其中年轻代又被划分了三块,Eden、Survivor1、Survivor2,它们的比例为 8:1:1
- Eden 区域和 Survivor1 区域用于存放对象
- Survivor2 区域备用。
(3)GC 的逻辑如下:
- 我们创建的对象,首先会放入 Eden 区域,如果 Eden 区域满了,那么就会触发一次 Minor GC,进行年轻代的垃圾回收(其实就是回收 Eden 区域内没有人使用的对象),然后将存活的对象存入 Survivor1 区域,再创建对象的时候继续放入 Eden 区域。
- 当第二次 Eden 区域满了,那么 Eden 和 Survivor1 区域中存活的对象,会一块被移动到 Survivor2 区域中。然后 Survivor1 和 Survivor2 的角色调换,Survivor1 变成了备用。
- 当第三次 Eden 区域再满了的时候,Eden 和 Survivor2 区域中存活的对象,会一块被移动到 Survivor1 区域中,按照这个规律进行循环。
- 如果一个对象,在年轻代中,撑过了多次垃圾回收(默认是 15 次),都没有被回收掉,那么会被认为是长时间存活的,此时就会被移入老年代。此外,如果在将 Eden 和 Survivor1 中的存活对象,尝试放入 Survivor2 中时,发现 Survivor2 放满了,那么会直接放入老年代。此时就出现了,短时间存活的对象,也会进入老年代的问题。
- 如果老年代的空间满了,那么就会触发 Full GC,进行老年代的垃圾回收操作,如果执行 Full GC 也释放不了内存空间,就会报内存溢出的错误了。
注意:Full GC 是一个重量级的垃圾回收,Full GC 执行的时候,程序是处于暂停状态的,这样会非常影响性能。
2,查看 Spark 任务的GC时间
(1)我们可以对 task 的垃圾回收进行监测,在 spark 的任务执行界面,可以查看每个 task 执行消耗的时间,以及 task gc 消耗的时间。
提示:要查看监测信息,需确保 Hadoop 集群、yarn 的 historyserver 进程以及 spark 的 historyserver 进程是正常运行的。
(2)例如我提交一个任务后,点击生成的第一个 job,再点击进去查看这个 job 的 stage,进入第一个 stage,查看 task 的执行情况,看这里面的 GC time 的数值会不会比较大,如果 gc time 这里标红了,则说明 gc 时间过长。
(3)上面这个是分任务查看,我们还可以查看全局的,看 Executor 进程中整个任务执行总时间和 gc 的消耗时间。
附:优化方法
1,提高 Executor 的内存
(1)最直接的方法就是提高 Executor 的内存,我们可以在 spark-submit 中通过参数指定 executor 的内存。
(2)例如下面命令我们指定每个 executor 的内存大小为 4GB:
spark-submit \ --class com.example.MyApp \ --master yarn \ --deploy-mode cluster \ --executor-memory 4G \ /path/to/myapp.jar \ input_path output_path
2,调整 Eden 与 s1 和 s2 的比值(不推荐)
(1)我们可以在 spark-submit 脚本中通过 --conf 参数设置 Eden 与 s1 和 s2 的比值:
- -XX:NewRatio=4:设置年轻代(包括 Eden 和两个 Survivor 区)与年老代的比值(除去持久代)设置为 4,则年轻代与年老代所占比值为 1:4,年轻代占整个堆栈的 1/5
- -XX:SurvivorRatio=4:设置年轻代中 Eden 区与 Survivor 区的大小比值设置为 4,则两个 Survivor 区与一个 Eden 区的比值为 2:4,一个 Survivor 区占整个年轻代的 1/6
提示:通常来说我们很少需要去调整 Eden、s1、s2 的比值,一般都是直接增加 Executor 的内存比较靠谱。因为如果内存上不去,其它的修改都是徒劳。
(2)下面时具体的样例命令:
spark-submit \ --class com.example.MyApp \ --master yarn \ --deploy-mode cluster \ --executor-memory 4G \ --conf "spark.executor.extraJavaOptions=-XX:NewRatio=4 -XX:SurvivorRatio=4" \ /path/to/myapp.jar \ input_path output_path
3,调整 RDD 数据存储占用内存的比例
(1)默认情况下,Spark 使用每个 executor 60% 的内存空间来缓存 RDD,那么只有 40% 的内存空间来存放算子执行期间创建的对象
- 在这种情况下,可能由于内存空间的不足,并且算子对应的 task 任务在运行时创建的对象过大,那么一旦发现 40% 的内存空间不够用了,就会触发 Java 虚拟机的垃圾回收操作。因此在极端情况下,垃圾回收操作可能会被频繁触发。
- 因此如果发现垃圾回收频繁发生。那么就需要对这个比例进行调优了
(2)spark.storage.memoryFraction 参数的值默认是 0.6。下面我们将 RDD 缓存占用内存空间的比例降低为 50%,从而提供更多的内存空间来保存 task 运行时创建的对象。
spark-submit \ --class com.example.MyApp \ --master yarn \ --deploy-mode cluster \ --conf "spark.storage.memoryFraction=0.6" \ /path/to/myapp.jar \ input_path output_path
提示:对于 RDD 持久化而言,完全可以使用 Kryo 序列化,加上降低其 executor 内存占比的方式,来减少其内存消耗。给 task 提供更多的内存,从而避免 task 在执行时频繁触发垃圾回收。
全部评论(0)