暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

分析JVM,解析sparkOOM原因

数仓学习分享 2021-04-15
1170

    在大数据场景下,我们的Spark、Flink等大数据计算引擎都是跑在jvm虚拟机上的,OOM是我们经常遇到的错误信息,要解决这个错误,要求我们对JVM的模型有一些基本了解,下面我们来分析下JVM模型,并且分析下Spark中常见OOM的原因。

JVM的有两种模式分别为

JVM Server模式

JVM Client模式(windows32位操作系统,或者配置比较低的其它类型操作系统)。

mixed mode 混合模式

Java可以是解释型 int ,也可以是编译型comp,我们可以通过参数去设置。

JDK7:永久代--》JDK8:Metaspace(jdk7到jdk8的变化)

JVM的参数有三种类型:

    1、标准:从jvm最开始的版本到现在没有发生太大变化

    2、X:java -Xint  -version 相对变化较少的参数类型

    3、XX:变化较多 JVM调优的重点,XX    参数又分为两种,分别是

                a)boolean :-XX:[+/-]name(通过+/-号控制参数是否启用)

                        -XX:+UseG1GC  -XX:-UseG1GC

                 两个命令:jps=>pid

                                  jinfo -flag name pid

                                  jinof -flags pid

                b)非boolean(通过k-v的格式来设置参数)

                        -XX: name=value

                        -XX:MetaspaceSize=128m

PrintFlags系列

-XX:+PrintFlagsInitial

-XX:+PrintFlagsFinal

=默认值
:=修改过的、

几个特殊的XX参数

-Xmx min -XX:InitialHeapSize  1/64的menmory

-Xms max -XX: MaxHeapSize   1/4的menmory

-Xss           -XX:ThreadStackSize  0?

这些是XX参数

MaxTenuringThreshold 新生代到老年代的次数是15次


    运行时数据区
jvm定义了很多数据区,分为下面两种,jvm共享的,和每个线程独享的
jvm创建的时候创建,退出时销毁
每个Thread独有,Thread创建时创建,Thread退出时销毁

1)The pc Register 程序计数器 --每个thread独有
    占用一小块内存

    当前线程所执行的字节码的行号指示器
    多线程 每个线程都有自己的程序计数器
    字节码 不同的字节码指令干不同的东西 不同的字节码指令让程序干不同的事情

    每个线程都有自己的程序计数器

2)Java Virtual Machine Stacks  --每个线程独有
    方法里面的局部变量 frames:栈贞
    一个frame会被创建 当一个方法被调用的时候创建
    在方法执行完的时候会被销毁
    栈:main==>hello 入栈  出栈
    抛出的异常:StackOverflowError
    调用方法是,会为每个方法创建Frame 入栈
    方法执行完毕后 frame   出栈
    递归没出口 死循环会造成这个错误。

3)heap 堆 --所有jvm共享的区域
      new 创建对象 数组是会在heap里,

      当frame出栈以后 heap里的东西没有被销毁

      GC回收对象 当我们在的对象Frames出栈之后,heap里的对象是没有源头指向的,所以需要GC
      抛出的异常:OOM OutOfMemory Error 不停的new 比heap大小还大 会OOM

4)Method Area 1.8之后MetaSpace 所有JVM线程共享的
    每一个线程的结构  常量池

       方法、代码、构造器
    .class加载在这里
    抛出的异常:OutOfMemoryError

5)Run-Time Constant Pool 存在 Method Area

6)Nativ Method Stacks
    native修饰的方法 是调用本地操作系统里面的方法
    StackOverflowError/OutOfMemoryError

Area:不属于jvm 属于堆外内存 spark, 直接去操作内存空间
DirectByteBuffer.java类


运行时数据区是一个规范:
JVM内存结构是一个实现
    堆区     GC √
            新生代 Yong(S0 S1 Eden)
            老年代
    非堆区
            metaspace ccs codecache
谈谈你遇到过的常见的jvm相关的异常信息:

堆管存储、栈管执行   

1)java.lang.OutOfMemoryError: Java heap space:这个oom是在我们的heap里的,我们知道我们heap里存储的是我们new出来的对象,所以我们可以通过参数 -Xmx -Xms参数来调节;

2)java.lang.OutOfMemoryError: GC overhead limit exceeded:当GC为释放很小空间占用大量时间时抛出;一般是因为堆太小,导致异常的原因,没有足够的内存。【解决方案】:
  1、查看系统是否有使用大内存的代码或死循环;
  2、通过添加JVM配置,来限制使用内存:
  < jvm-arg>-XX:-UseGCOverheadLimit< jvm-arg>

3)  java.lang.OutOfMemoryError: Direct buffer memory。这个是我们用代码直接分配本地的内存,这个内存太小,我们可以手动设定。

调整-XX:MaxDirectMemorySize= 参数,如添加JVM配置:
  < jvm-arg>-XX:MaxDirectMemorySize=128m< jvm-arg>

4)  java.lang.OutOfMemoryError: unable to create new native thread

【原因】:Stack空间不足以创建额外的线程,要么是创建的线程过多,要么是Stack空间确实小了

5)  java.lang.StackOverflowError

这也内存溢出错误的一种,即线程栈的溢出,要么是方法调用层次过多(比如存在无限递归调用),要么是线程栈太小。


1、当我们编写的java程序被编译成.class文件后,会有类加载器将我们的.class文件加载到方法区里面

2、当方法被调用的时候会进入我们的虚拟机栈,虚拟机栈里面会有方法的局部变量,这个是对象的一个引用,这个引用会指向我们的heap区。

3、程序计数器,记录了我们每一个线程的执行到的字节码的位置。

Spark里的OOM错误

    我们先来看下Spark的内存模型


jvm堆内(heap)的内存分为四个部分(spark.memory.fraction=0.6)

reservedMemory:预留内存300M,用于保障spark正常运行

other memory:用于spark内部的一些元数据、用户的数据结构、防止在稀疏和异常大的记录的情况下出现对内存估计不足导致oom时的内存缓冲

execution:用于spark的计算:shuffle、sort、aggregation等这些计算时会用到的内存

storage:主要用于rdd的缓存

spark的OOM错误可能发生在:

Driver端的oom

    1、不合适的api调用

    2、广播了大变量

 Excutor端的OOM错误

    1、低效的查询

    2、不合适的Driver端和Executor端内存

    3、不合适的YARN Container内存

    4、内存中缓存了大量数据

    5、不合适的任务并行度

Driver端OOM Error

Driver端内存是通过配置“spark.driver.memory”来指定的,默认1g

1. 不适合的API调用

针对这种情况,我们有以下3中解决方法:

  1. 最简单粗暴的就是增加Driver端内存。

  2. 在RDD/Dataset上调用repartition()方法,将数据交给Executor上的一个任务处理。因为一般来讲,Executor会设置较多的内存。

  3. 可以设置“spark.driver.maxResultSize”(默认1g)来避免Driver出现OOM errors。

2. 广播了大变量

将RDD/Dataset进行广播时,会先将它们发送到Driver,然后由Driver端分发给每个Executor,如果我们的应用程序中广播了多个大变量,超出了Driver内存限制,就可能造成OOM Error。

针对这种情况,我们有以下2中解决方法:

  1. 增加Driver端内存。

  2. 通过配置“spark.sql.autoBroadcastJoinThreshold”减小要广播的变量的大小。

Executor端OOM Error

Executor端内存是通过配置“spark.executor.memory”来指定的,默认1g。

1. 低效的查询

比如,在列式存储格式的表上执行select * from t返回不必要的列;没有提前使用过滤条件过滤掉不必要的数据。

所以,我们在查询的时候,要尽量将过滤条件前置;要尽量只读需要的列;分区表上只查询指定分区的数据等等。

2. 不合适的Driver端和Executor端内存

每个Spark应用程序所需的内存都是不一样的,因此我们要结合所处理的数据的大小和应用监控(比如,Spark Web UI)来合理的为每个应用配置合适的Driver端和Executor端内存大小。

4. 内存中缓存大量数据

如果Spark应用程序指定在内存中缓存了大的RDD/Dataset,或者是缓存了较多的RDD/Dataset,就有可能发生OOM Error:

Spark内存模型中,Execution内存和Storage内存的总大小为0.6*(heap space - 300MB),这总大小里面两者默认各占一半,我们应用中缓存的哪些数据集正是存储在Storage内存区域的。如果Spark应用程序中计算较少,那么我们可以通过spark.memory.storage适当调大Storage内存,或者通过配置spark.memory.fraction整体调大两者的总内存。

5. 不合适任务并行度

假如我们的Spark应用程序中要读取的表或文件数据量比较大,而我们没有配置合适的内存大小,并且分配了比较高的并发度和CPU核数,这些数据要在内存中计算,这就可能会造成Executor端OOM。

如果任务并行度较低,而某个任务又被分配了较多的数据,也会造成OOM。


文章转载自数仓学习分享,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论