【Spark精讲】Spark内存管理

2023-12-13 16:12:09

前言

Spark的Driver和Executor作为一个JVM进程,其内存管理是建立在JVM的内存管理之上的。

Java内存管理

Java运行时数据区

  • 方法区:它用于存储每个类的结构信息,如运行时常量池、字段和方法数据、构造函数等内容。可共各个线程共享的内存区域;
  • Java堆:所有类的实例和数组对象分配的内存区域,这个区域是所有线程共享的内存区域;
  • PC寄存器:用于CPU运行多线程时,记录每个线程Java虚拟机正在执行的字节码指令的地址;
  • Java虚拟机栈:每个线程都有自己私有的Java虚拟机栈。随线程创建而创建,随线程消失而销售。每个线程中,方法的调用都是通过Java虚拟机栈传递的。每个方法的调用都会生成对应的栈帧(Frame)压栈,方法执行结束时,对应的栈帧出栈,从而形成一些列的方法调用过程;
  • 栈帧:是一种用来存储数据和部分过程结果的数据结构,同时栈帧也用来处理动态链接、方法返回值和异常的派发;
  • 本地方法栈:用传统的栈来支持native方法的执行。创建线程时按线程分配。

Java堆

Java堆内存包含两部分:

  1. 新生代:包括Eden区,From Survivor区(S0),To?Survivor区(S1),Eden:S0:S1 = 8:1:1
  2. 老年代:新生代 : 老年代 = 1 : 2,即年轻代占整个堆内存的1/3,老年代占2/3

通常对象在Eden区分配,经过一次新生代垃圾回收后,存活的对象被整理到S0或者S1,同时对象的年龄会加1,对象的年龄达到一定条件后,会进入老年代。

垃圾回收机制

垃圾回收(Garbage Collection,GC)针对的是Java堆和方法区。

Java堆和方法区两部分区域的生命周期是和整个Java应用程序相关联的,需要进行垃圾回收。而Java运行时数据区的其他部分如Java虚拟机栈,Java本地方法栈和PC寄存器是与每个线程的生命周期关联的,随线程创建与消亡,不需要进行垃圾回收。

判断一个对象是否回收的方法:可达性分析。找到一些列根对象,一般的根对象可以是Java虚拟机栈引用对象、方法区中静态属性和常量引用对象、本地方法栈引用对象等。以这一系列根对象为起点,向下搜索其引用的对象,当一个对象从根对象不可达时,则认为这个对象可以被回收了。

Java的引用共分为4种:强引用、软引用、弱引用、虚引用,引用关系强度依次减弱。

  • 强引用:垃圾回收器不会回收。
  • 软引用:发生内存溢出前,会把这类对象列入回收范围之内进行二次回收。回收后内存还是不足,则抛出内存溢出的异常。SoftReference。
  • 弱引用:垃圾回收时,无论内存是否充足都会回收。WeakReference。
  • 虚引用:不对垃圾回收产生影响。只是为了垃圾回收时,能够收到一个系统的通知。

垃圾回收算法:

  1. 引用计数法:缺点是无法处理循环引用,每次新增引用或清除引用时都要加减操作影响性能;
  2. 标记清除算法:分标记阶段和清除阶段。缺点是垃圾回收后会产生大量的内存碎片,影响内存分配效率;
  3. 标记压缩算法:在标记清除算法基础上,垃圾清理之后,将剩余存活的对象进行一次整理,统一移动到连续的内存空间中,虽然解决了内存不连续的问题,但是在压缩阶段会有额外的移动工作;
  4. 复制算法:分S0和S1两块内存区域,每次使用其中一个区域,当使用区域用完时,执行垃圾回收,将存活的对象复制到另外一个区域,然后将已使用的这块区域清空。优点是分配速度快,运行高效,缺点是内存浪费太严重。
  5. 分区算法:将整个堆空间划分成多个连续的更小的空间,每个空间独立分配独立回收。可以有效控制GC停顿时长;
  6. 分代思想:对象分两种一种存在生命周期较短的,一种生命周期较长的,相应的把内存划分为新生代存储生命周期较短的,老年代存储生命周期较长的。老年代适合用标记清除算法、标记压缩算法,新生代适合复制算法。新生代划分为eden、from、to三个区,from和to也称为幸存区(survive),分别为S0、S1。

垃圾回收器:

  1. 串行收集器:单线程运行,会暂停应用中其他线程直至回收完毕,可用于新生代和老年代;
  2. 并行收集器:多线程并行运行,分为ParNew(新生代)、ParallelGC(新生代)、ParallelOldGC(老年代);
  3. CMS收集器:CMS(并发标记清除),以获得最短垃圾回收时间为目标,减少系统停顿,一般应用在一些Web服务中。
  4. G1垃圾收集器:面向服务端应用,充分利用多核CPU的优势,缩短垃圾回收时用户线程停顿时长,回收过程与CMS类似,但可与用户线程同时执行。可用于整堆内存回收。

Executor内存管理

静态内存管理

统一内存管理

?

可用内存计算

参考:Spark内存管理计算详述-CSDN博客

常见问题?

SparkSQL导致的JVM栈内存溢出

当 SparkSQL 的 sql 语句有成百上千的 or 关键字时,就可能会出现Driver端的JVM栈内存溢出。

JVM 栈内存溢出基本上就是由于调用的方法层级过多,产生了大量的,非常深的,超 出了 JVM 栈深度限制的递归。(我们猜测 SparkSQL 有大量 or 语句的时候,在解析 SQL 时, 例如转换为语法树或者进行执行计划的生成的时候,对于 or 的处理是递归,or 非常多时,会发生大量的递归)。此时,建议将一条 sql 语句拆分为多条 sql 语句来执行,每条 sql 语句尽量保证 100 个以内的子句。根据实际的生产环境试验,一条 sql 语句的 or 关键字控制在 100 个以内,通 常不会导致 JVM 栈内存溢出。

文章来源:https://blog.csdn.net/weixin_40035038/article/details/134967913
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。