云服务器价格_云数据库_云主机【优惠】最新活动-搜集站云资讯

云服务器_数据库即服务_哪家好

小七 141 0

为apachespark应用程序调优Java垃圾收集

这是我们在英特尔的SSG-STO大数据技术组的朋友的一篇客座帖子。加入我们的Spark峰会,听听Intel和其他公司在生产中部署ApacheSpark的消息。使用代码数据库20可以获得20%的折扣!apachespark由于其卓越的性能、简单的接口和丰富的分析和计算库而获得了广泛的行业采用。像大数据生态系统中的许多项目一样,Spark运行在Java虚拟机(JVM)上。因为Spark可以在内存中存储大量数据,它主要依赖于Java的内存管理和垃圾收集(GC)。像钨丝计划这样的新计划将在未来的Spark版本中简化和优化内存管理。但是今天,了解Java的GC选项和参数的用户可以对其进行调优,以获得Spark应用程序的最佳性能。本文描述了如何为Spark配置JVM垃圾收集器,并给出了实际用例,解释了如何优化GC以提高Spark的性能。我们研究了调优GC时的关键考虑事项,例如收集吞吐量和延迟。火花和垃圾收集简介随着Spark在工业中的广泛应用,Spark应用程序的稳定性和性能调整问题越来越受到人们的关注。由于Spark以内存为中心的方法,使用100GB或更多内存作为堆空间是很常见的,这在传统Java应用程序中很少见到。在与使用Spark的大公司合作时,我们收到了很多关于Spark应用程序执行期间围绕GC的各种挑战的担忧。例如,垃圾收集需要很长时间,导致程序出现长时间延迟,甚至在严重情况下崩溃。在本文中,我们使用真实的例子,结合具体问题,讨论Spark应用程序的GC调优方法,可以缓解这些问题。Java应用程序通常使用两种垃圾收集策略之一:并发标记清除(CMS)垃圾收集和ParallelOld垃圾收集。前者的目标是降低延迟,而后者的目标是提高吞吐量。这两种策略都存在性能瓶颈:cmsgc不进行压缩[1],而并行GC只执行整个堆压缩,这会导致相当长的停顿时间。在英特尔,我们建议客户选择最适合特定应用程序要求的策略。对于具有实时响应的应用程序,我们通常推荐CMS-GC;对于离线分析程序,我们使用并行GC。所以对于Spark这样既支持流计算又支持传统批处理的计算框架,我们能找到一个最佳的收集器吗?hotspotsjvm版本1.6为垃圾收集引入了第三个选项:垃圾优先GC(g1gc)。甲骨文计划将G1收集器作为cmsgc的长期替代品。最重要的是,G1收集器的目标是实现高吞吐量和低延迟。在详细介绍如何使用带有Spark的G1收集器之前,让我们先回顾一下javagc基础知识的一些背景知识。Java的垃圾收集器如何工作在传统的JVM内存管理中,堆空间分为年轻代和老年代。年轻一代由一个名为Eden的区域和两个较小的幸存者空间组成,如图1所示。新创建的对象最初在Eden中分配。每次小GC发生时,JVM将Eden中的活动对象复制到一个空的幸存者空间,并且还复制另一个幸存者空间中的活动对象,该幸存者空间正被用于该幸存者空间。此方法将一个幸存者空间保留为对象,另一个为空以供下一个集合使用。保留了一些小集合的对象将被复制到旧代。当旧代填满时,主要GC将挂起所有线程来执行完整的GC,即组织或删除旧代中的对象。当所有线程都挂起时,这种执行暂停被称为Stop The World(STW),它牺牲了大多数GC算法的性能。[2]图1代热点堆结构[2]**Java新的g1gc彻底改变了传统的方法。堆被划分为一组大小相等的堆区域,每个区域都是一个连续的虚拟内存范围(图2)。某些区域集被分配与旧收集器中相同的角色(Eden、survivor、old),但它们没有固定的大小。这为内存使用提供了更大的灵活性。当一个对象被创建时,它最初被分配到一个可用的区域中。当区域填满时,JVM创建新的区域来存储对象。当发生小GC时,G1将活动对象从堆的一个或多个区域复制到堆上的单个区域,并选择一些空闲的新区域作为Eden区域。只有当所有区域都包含活动对象并且找不到完整的空区域时才会发生完全GC。G1在标记活动对象时使用记住的集合(RSets)概念。资源集通过外部区域跟踪对象引用到给定区域。堆中每个区域有一个资源集。RSet避免了整个堆扫描,并允许对一个区域进行并行和独立的收集。在这种情况下,我们可以看到,g1gc不仅在触发full-GC时大大提高了堆的占用率,而且使较小的GC暂停时间更加可控,因此对于大内存环境非常友好。这些破坏性的改进如何改变GC的性能?这里我们使用最简单的方法来观察性能变化,即从旧的GC设置迁移到g1gc设置。[3]图2 G1堆结构示意图[3]**由于G1放弃了对年轻/过时对象使用固定堆分区的方法,因此我们必须相应地调整GC配置选项,以保证G1收集器应用程序的平稳运行。与旧的垃圾收集器不同,我们通常发现G1收集器的一个好的起点是不执行任何调优。因此,我们建议只从默认设置开始,通过-XX:+UseG1GC选项启用G1。我们发现有时有用的一个调整是,当一个应用程序使用多个线程时,最好使用-XX:-ResizePLAB关闭PLAB()resize并避免大量线程通信导致的性能下降。要获得hotspotsjvm支持的GC参数的完整列表,可以使用参数-XX:+PrintFlagsFinal来打印该列表,或者参考Oracle官方文档来获取部分参数的解释。了解Spark中的内存管理弹性分布式数据集(RDD)是Spark的核心抽象。RDD的创建和缓存与内存消耗密切相关。Spark允许用户持久地缓存数据以便在应用程序中重用,从而避免了重复计算造成的开销。持久化RDD的一种形式是在JVM堆中缓存全部或部分数据。Spark的执行器将JVM堆空间分成两部分:一部分用于存储Spark应用程序持久缓存到内存中的数据;另一部分用作JVM堆空间,负责RDD转换期间的内存消耗。我们可以使用火花存储记忆提取参数使Spark控制缓存RDD的总大小,方法是确保它不超过RDD堆空间体积乘以此参数的值。JVM也可以使用RDD缓存部分的未使用部分。因此,Spark应用程序的GC分析应该包括两个内存部分的内存使用情况。当观察到GC延迟导致的效率下降时,我们应该首先检查并确保Spark应用程序有效地使用了有限的内存空间。RDD占用的内存空间越少,留给程序执行的堆空间就越多,这就提高了GC的效率;相反,RDD占用的内存过多,会导致旧代中大量的缓冲对象导致性能显著下降。在这里,我们用一个用例来扩展这一点:例如,用户有一个基于Spark的Bagel组件的应用程序,它执行简单的迭代计算。一个超级步骤(迭代)的结果取决于前一个超级步骤的结果,因此每个超级步骤的结果将被保存在内存空间中。在程序执行期间,我们观察到当迭代次数增加时,progress使用的内存空间迅速增长,导致GC变得更糟。当我们仔细观察百吉饼时,我们发现它会将每个超级步骤的RDD缓存在内存中,而不会随着时间的推移而释放它们,即使它们在一次迭代之后没有被使用。这会导致内存消耗增长,从而触发更多GC尝试。我们删除了SPARK-2661中不必要的缓存。在这个修改缓存之后,RDD大小在三次迭代后趋于稳定,缓存空间现在得到了有效的控制(如表1所示)。结果表明,气相色谱效率大大提高,程序总运行时间缩短10%~20%。表1:优化前后百吉饼应用程序RDD缓存大小的比较迭代次数每次迭代的缓存大小总缓存大小(优化前)缓存总大小(优化后)初始化4.3GB4.3GB4.3GB18.2GB12.5 GB8.2GB298.8GB111.3 GB98.8GB三90.8GB202.1 GB90.8GB 结论:当观察到GC过于频繁或持续时间过长时,可能表示Spark进程或应用程序没有有效地使用内存空间。您可以在不再需要缓存的RDD之后显式地清理它们,从而提高性能。选择垃圾收集器如果我们的应用程序尽可能高效地使用内存,下一步就是调整垃圾收集器的选择。在实现SPARK-2661之后,我们建立了一个四节点集群,为每个执行器分配一个88GB的堆,并以独立模式启动SPARK来进行我们的实验。我们从默认的Spark Parallel GC开始,发现由于Spark应用程序的内存开销相对较大,而且