探寻内存溢出(OOM)

      OOM为out of memory的简称,称之为内存溢出,来源于java.lang.OutOfMemoryError。当JVM因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时,就会抛出这个error(注:非exception,因为这个问题已经严重到不足以被应用处理)。
     既然是内存溢出,那我们就先看看JVM有哪些内存区间。之前文章提到过,Java虚拟机在运行时,会把内存划分为若干不同的数据区域,主要有以下几部分:堆、方法区、栈、直接内存。

一.内存溢出(OOM)的原因

1.堆溢出

     java堆内存溢出,堆上分配的对象越来越多,有没有及时回收,引起内存溢出。此种情况最常见,一般由于内存泄露或者堆的大小设置不当引起。
代码例子:
public static void main(String args[]){
    ArrayList<byte[]> list=new ArrayList<byte[]>();
    for(int i=0;i<1024;i++){
        list.add(new byte[1024*1024]);
    }
}

解决方法:
a.增大堆空间
b.及时释放内存

2.方法区溢出

     方法区用于保存已被虚拟机加载的类信息,这部分内存溢出,一般出现于大量Class或者jsp页面,或者采用cglib等反射机制的情况,因为上述情况会产生大量的Class信息存储于方法区。
代码例子:(生成大量的类)
public static void main(String[] args) {
    for(int i=0;i<100000;i++){
        CglibBean bean = new CglibBean("geym.jvm.ch3.perm.bean"+i,new HashMap());
    }
}

解决方法:
a.增大Perm区
b.设置允许Class回收

3.栈溢出

栈溢出两种情况:

1、创建线程过多,抛出OutOfMemoryError

2、线程请求的栈深度大于虚拟机允许的最大深度 StackOverflowError

第一种栈溢出指,在创建线程的时候,需要为线程分配栈空间,这个栈空间是向操作系统请求的,如果操作系统无法给出足够的空间,就会抛出OOM。
代码例子:(设置-Xss1m,表示每创建1个线程需要分配1MB的空间)
public static class SleepThread implements Runnable{
    public void run(){
        try {
            Thread.sleep(10000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public static void main(String args[]){
    for(int i=0;i<1000;i++){
        new Thread(new SleepThread(),"Thread"+i).start();
        System.out.println("Thread"+i+" created");
    }
}

解决方法:
a.减少堆内存,因为堆空间和线程栈空间大小总和不能大于操作系统的可分配内存,因此减少堆空间可为栈节约出空间。
b.减少线程栈大小,通过-Xss将单个线程栈空间缩小,就能创建更多的线程,当然前提是单个线程栈空间够用。

第二种栈溢出指StackOverflowError。 是一个java中常出现的错误。在jvm运行时的数据区域中有一个java虚拟机栈,当执行java方法时会进行压栈弹栈的操作。在栈中会保存局部变量,操作数栈,方法出口等等。

JVM的内存结构我们可知:

  • 随着线程栈的大小越大,能够支持越多的方法调用,也即是能够存储更多的栈帧;
  • 局部变量表内容越多,那么栈帧就越大,栈深度就越小。

jvm设置限制了栈的最大深度,当执行时栈的深度大于了允许的深度,就会抛出StackOverflowError错误。一般在递归的时候,递归层级过多,导致栈溢出。

然而,递归并不是导致此错误的唯一原因。在应用程序不断从方法内调用方法直到堆栈耗尽的情况下,也可能发生这种情况。这是一种罕见的情况,因为没有开发人员会故意遵循糟糕的编码实践。

另外一种可能的原因是方法中有大量局部变量,因为局部变量表内容越多,那么栈帧就越大,栈深度就越小。造成虚拟机在扩展栈深度时,无法申请到足够的内存空间。

当应用程序设计为类之间具有循环关系时,也可以抛出StackOverflowError。在这种情况下,会重复调用彼此的构造函数,从而引发此错误。这也可以被视为递归的一种形式。

解决方法:如果程序没有问题,确实需要较大的调用深度,可以通过调整-Xss增加栈内存。

4.直接内存溢出

       直接内存并不是虚拟机运行时数据区的一部分,也不是Java 虚拟机规范中定义的内存区域。在JDK1.4 中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O 方式,它可以使用native 函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。

     操作系统可分配给JVM的内存是一定的,这部分内存要被堆空间、线程栈空间和直接内存瓜分。这三块空间加起来,是不能大于操作系统可分配内存的。直接内存的分配不会受到Java 堆大小的限制,受到本机总内存大小限制,它也有可能出现OutOfMemoryError。
代码例子:
for(int i=0;i<1024;i++){
    ByteBuffer.allocateDirect(1024*1024);
    System.out.println(i);
      System.gc();
}

解决方法:
a.减少堆内存,道理同栈溢出
b.有意触发GC,直接内存的过度使用会导致OOM,但不会触发GC,只有堆和方法区的分配会导致GC发生。直接内存不触发GC,但却可以通过GC进行回收,因此可以人工触发GC,进行直接内存的回收。

二.内存溢出(OOM)分析

      当发生OOM时,需要借助工具进行分析。常用的如Memory Analyzer(MAT)、Visual VM等。使用MAT进行分析时,要理解以下基础概念:
1.浅堆:
        一个对象结构所占用的内存大小。
以String类型为例:

3个int类型以及一个引用类型合计占用内存3*4+4=16个字节。再加上对象头的8个字节,因此String对象占用的空间,即浅堆的大小是16+8=24字节。对象大小按照8字节对齐,一定是8的倍数。
注意:浅堆大小和对象的内容无关,只和对象的结构有关。

2.深堆:
      一个对象被GC回收后,可以真实释放的内存大小。也就是只能通过该对象访问到的(直接或者间接)所有对象的浅堆之和。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 1. Spark中JVM内存使用及配置详情: Spark中的JVM内存使用主要包括堆内存和非堆内存。堆内存用于存储对象实例,而非堆内存用于存储类信息、方法信息等。在Spark中,可以通过以下参数来配置JVM内存使用: - spark.driver.memory:用于配置Driver进程的堆内存大小,默认为1g。 - spark.executor.memory:用于配置Executor进程的堆内存大小,默认为1g。 - spark.driver.extraJavaOptions:用于配置Driver进程的非堆内存大小和其他JVM参数。 - spark.executor.extraJavaOptions:用于配置Executor进程的非堆内存大小和其他JVM参数。 2. Spark报错与调优: 在Spark运行过程中,可能会出现各种报错,如内存溢出、任务失败等。针对这些报错,可以采取以下调优措施: - 内存溢出:增加Executor进程的堆内存大小、减少每个任务的数据量、使用缓存等方式来减少内存使用。 - 任务失败:增加Executor进程的数量、减少每个任务的数据量、调整任务的并行度等方式来提高任务的执行效率。 3. Spark内存溢出OOM异常: Spark内存溢出OOM异常是指Executor进程的堆内存不足以存储当前任务所需的数据,导致任务执行失败。可以通过增加Executor进程的堆内存大小、减少每个任务的数据量、使用缓存等方式来减少内存使用,从而避免内存溢出异常的发生。 ### 回答2: Spark中JVM内存使用及配置详情: Spark使用JVM来执行任务,其中一个非常重要的参数是堆内存(Heap Memory)的大小。堆内存用于存储对象实例和方法调用的信息。在使用Spark时,可以通过spark.driver.memory和spark.executor.memory参数来配置JVM堆内存的大小,默认情况下,它们都是1g。需要根据具体的任务需求和集群资源情况来进行调整。如果遇到内存不足的情况,可以增加堆内存的大小,但是需要保证集群资源充足。 Spark报错与调优: 在使用Spark过程中,常见的报错有内存溢出、数据倾斜、任务运行时间过长等问题。对于这些问题,可以采取一些调优策略进行处理。例如,在遇到内存溢出(Out of Memory)异常时,可以通过增加堆内存大小或者减少数据量来解决;对于数据倾斜的情况,可以考虑数据重分区或者使用一些聚合策略来优化;对于任务运行时间过长的情况,可以考虑增加Spark任务的并行度或者使用缓存机制来加速计算等。 Spark内存溢出OOM)异常: Spark中的内存溢出异常通常是由于使用的内存超过了配置的阈值引起的。在配置Spark应用程序时,可以设置spark.driver.memory和spark.executor.memory参数来调整JVM堆内存的大小。如果内存不足,则需要增加内存配置或者优化代码逻辑。另外,可以通过设置spark.memory.offHeap.enabled参数来开启堆外内存,将一部分内存放到堆外,从而减少对JVM堆内存的占用。此外,还可以通过设置spark.memory.fraction参数来调整JVM堆内存的分配比例,更好地利用内存资源。如果调整参数后仍然出现内存溢出问题,还可以考虑调整Spark任务的并行度或者增加集群资源。 ### 回答3: Spark是一个基于内存的数据处理框架,能够高效地处理大规模数据集。在Spark中,JVM内存的使用及配置对于保证程序的稳定和性能的提升非常重要。 首先,Spark的JVM内存分为堆内存和非堆内存两部分。堆内存是用来存储对象实例的,而非堆内存则用来存储JVM本身的运行时数据。为了合理配置JVM内存,可以通过配置spark.driver.memory和spark.executor.memory参数来设置堆内存的大小。根据集群的硬件配置和任务的需求情况,可以根据具体情况来调整这两个参数的数值。 其次,在Spark运行过程中,经常会遇到各种报错。常见的报错有内存溢出(OutOfMemoryError)、任务失败(TaskFail)等。当遇到内存溢出错误时,可以尝试以下几种方法来调优: 1. 增加可用内存:可以通过增加executor内存或调整任务分区大小来扩大可用内存。 2. 减少数据规模:可以通过过滤数据、采样数据或者使用压缩算法来减少数据的大小。 3. 优化代码:可以优化代码逻辑和算法,减少内存使用。 4. 调整缓存策略:可以通过手动控制缓存的数据量,及时释放不再使用的缓存。 最后,Spark的内存溢出OOM)异常通常是由于数据量过大,超出了可用内存的限制而导致的。当出现内存溢出异常时,可以参考上述的调优方法来解决问题。 总之,合理配置JVM内存、及时处理报错、避免内存溢出异常是保证Spark程序稳定与性能的关键。希望以上回答对您有所帮助。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值