【Spark性能优化】JVM调优原理概述以及降低cache内存占比

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_37142346/article/details/81839365

在Spark性能优化中包括几大块,前面主要讲述了常用的调优操作,比如RDD持久化,序列化等操作,本篇文章讲述另一块调优点,JVM调优,首先来了解一下JVM调优的原理。

JVM调优原理概述

了解JVM的程序员的都知道,JVM被划分为几个部分,比如虚拟机栈,本地方法栈,堆内存等。这里JVM调优主要针对堆内存,因此,我们着重介绍堆内存。

在JVM中,堆内存一般存放创建的一个对象。在我们常用的虚拟机中(比如Hotsopt虚拟机),将堆内存划分为两大部分年轻代(young generation)、老年代(old generation)。在年轻代中,通常被划分为三大块内存:Eden、Survivor1、Survivor2。它们之间的内存占比通常为8:1:1。

我们通常使用的虚拟机主要使用了分代垃圾回收算法。年轻代中主要使用复制算法,其主要原因主要因为年轻代中的对象大多生命周期比较短,每次GC需要收集大量不用的对象。在老年代中主要使用标记-整理算法进行收集,与年轻代相反的,老年代中大多存放一些生命周期比较长的对象,数量也不是很多,比如数据库连接池等。

在每一个创建对象时首先会将对象放入Eden区域以及其中的一个Survivor区域,比如Survivor1区域。当我们在创建的对象过多时,这两个区域内存不足时就会触发minorGC,它会将Eden以及Survivor1中的存活的对象复制到另一个Survivor2中,然后将不用的对象GC,为后面创建的对象腾出内存来使用。如果我们频繁的创建对象,就会频繁的GC,每次GC就会将存活的对象复制到另一个Survivor2中,当Survivor2中的内存溢满的时候,这时候JVM会使用分配担保机制将一些多余的对象直接放入老年代中。

当在一定时期,老年代中因为年轻代的满溢将对象放入老年代也满溢了,此时就会触发fullGC,由于老年代使用了标记-整理收集算法,该算法效率底下,每次进行full GC都会很耗时,严重影响系统的性能。

而且非常重要的一点是无论是minor GC还是full GC都会停止工作线程,直到GC完毕才会继续执行工作线程(官方称之为stop the world)。此时,可以想象我们的系统会因为频繁的GC而进行等待影响了作业的执行速度。

以上原因都是因为堆内存大小分配不足而导致年轻代与老年代满溢而发生GC操作,使得工作线程停止工作进行等待。在了解了其原理以及影响性能的原因之后,我们就可以做出调优操作。

降低cache内存占比

众所周知,Spark使用scala开发,scala是运行在jvm之上的编程语言,也就是说Spark运行在jvm之上,在Spark中,将堆内存划分为两部分,一部分用于给RDD数据cache、persist缓存使用,另一部分用于Spark算子函数中创建对象使用。

默认情况下,RDD数据缓存的内存占比为0.6,也就是60%的堆内存用于数据的持久化。此时如果Spark算子中创建了大量的对象就会导致频繁的GC,这时我们可以通过降低RDD缓存内存占比来降低GC操作,在实际项目中我们可以使用SparkUI来观察每个stage运行情况,包括task运行时间、gc时间等等,如果发现频繁的GC就可以进行调节内存的占比。

如何来调节RDD缓存的内存占比呢?可以通过设置spark.storage.memoryFraction参数来设置内存占比,默认的值为0.6,我们可以降低至0.5、0.4,甚至0.2来优化内存,防止频繁的GC使得Spark工作线程进行长时间的等待,给系统的性能带来严重影响,也可以通过-Xmn参数给Eden区域分配更大的内存,预计大小为4/3。

此时,我们主要讲述完了JVM调优原理以及如何利用这个原理来优化性能,如果对于JVM不了解的朋友可以多查查资料,也可以参考周志明的《深入浅出JVM虚拟机》来深入学习。本篇文章就介绍到这里,如有任何问题,欢迎留言讨论,谢谢!!!


猜你喜欢

转载自blog.csdn.net/qq_37142346/article/details/81839365
今日推荐