JVM相关 - StackOverflowError 与 OutOfMemoryError(下)

简介: JVM相关 - StackOverflowError 与 OutOfMemoryError(下)

3. OutOfMemoryError: unable to create native thread


这个在创建太多的线程,超过系统配置的极限。如Linux默认允许单个进程可以创建的线程数是1024个。

一般报这个错首先考虑不要创建那么多线程,线程池化并池子尽量同业务复用。如果实在要创建那么多线程,则考虑修改服务器配置:

//查看限制个数
ulimit -u
//编辑修改
vim /etc/security/limits.d/90-nproc.conf


4. OutOfMemoryError: GC Overhead limit exceeded


默认情况下,并不是等堆内存耗尽,才会报 OutOfMemoryError,而是如果 JVM 觉得 GC 效率不高,也会报这个错误。

那么怎么评价 GC 效率不高呢?来看下源码: 呢?来看下源码gcOverheadChecker.cpp

void GCOverheadChecker::check_gc_overhead_limit(GCOverheadTester* time_overhead,
                                                GCOverheadTester* space_overhead,
                                                bool is_full_gc,
                                                GCCause::Cause gc_cause,
                                                SoftRefPolicy* soft_ref_policy) {
  // 忽略显式gc命令,比如System.gc(),或者通过JVMTI命令的gc,或者通过jcmd命令的gc
  if (GCCause::is_user_requested_gc(gc_cause) ||
      GCCause::is_serviceability_requested_gc(gc_cause)) {
    return;
  }
  bool print_gc_overhead_limit_would_be_exceeded = false;
  if (is_full_gc) {
    //如果gc时间过长,并且gc回收的空间还是不多
    //gc时间占用98%以上为gc时间过长,可以通过 -XX:GCTimeLimit= 配置,参考gc_globals.hpp: GCTimeLimit
    //回收空间小于2%为gc回收空间不多,可以通过  -XX:GCHeapFreeLimit= 配置,参考gc_globals.hpp: GCHeapFreeLimit
    if (time_overhead->is_exceeded() && space_overhead->is_exceeded()) {
      _gc_overhead_limit_count++;
      //如果UseGCOverheadLimit这个状态位为开启
      //默认情况下,是开启的,可以通过启动参数-XX:-UseGCOverheadLimit关闭,参考:gc_globals.hpp: UseGCOverheadLimit
      if (UseGCOverheadLimit) {
        //如果超过规定次数,这个次数默认不可配置,必须开启develop编译jdk才能配置,参考gc_globals.hpp: GCOverheadLimitThreshold
        if (_gc_overhead_limit_count >= GCOverheadLimitThreshold){
          //设置状态位,准备抛出OOM
          set_gc_overhead_limit_exceeded(true);
          //清空计数
          reset_gc_overhead_limit_count();
        } else {
          //如果还没到达次数,但是也快到达的时候,清空所有的软引用
          bool near_limit = gc_overhead_limit_near();
          if (near_limit) {
            soft_ref_policy->set_should_clear_all_soft_refs(true);
            log_trace(gc, ergo)("Nearing GC overhead limit, will be clearing all SoftReference");
          }
        }
      }
      //需要打印日志,提示GC效率不高
      print_gc_overhead_limit_would_be_exceeded = true;
    } else {
      // Did not exceed overhead limits
      reset_gc_overhead_limit_count();
    }
  }
  if (UseGCOverheadLimit) {
    if (gc_overhead_limit_exceeded()) {
      log_trace(gc, ergo)("GC is exceeding overhead limit of " UINTX_FORMAT "%%", GCTimeLimit);
      reset_gc_overhead_limit_count();
    } else if (print_gc_overhead_limit_would_be_exceeded) {
      assert(_gc_overhead_limit_count > 0, "Should not be printing");
      log_trace(gc, ergo)("GC would exceed overhead limit of " UINTX_FORMAT "%% %d consecutive time(s)",
                          GCTimeLimit, _gc_overhead_limit_count);
    }
  }
}

默认配置:gc_globals.hpp

product(bool, UseGCOverheadLimit, true,                                   \
          "Use policy to limit of proportion of time spent in GC "          \
          "before an OutOfMemory error is thrown")                          \
                                                                            \
product(uintx, GCTimeLimit, 98,                                           \
      "Limit of the proportion of time spent in GC before "             \
      "an OutOfMemoryError is thrown (used with GCHeapFreeLimit)")      \
      range(0, 100)                                                     \
                                                                        \
product(uintx, GCHeapFreeLimit, 2,                                        \
      "Minimum percentage of free space after a full GC before an "     \
      "OutOfMemoryError is thrown (used with GCTimeLimit)")             \
      range(0, 100)                                                     \
                                                                        \
develop(uintx, GCOverheadLimitThreshold, 5,                               \
      "Number of consecutive collections before gc time limit fires")   \
      range(1, max_uintx)

可以总结出:默认情况下,启用了 UseGCOverheadLimit,连续 5 次,碰到 GC 时间占比超过 98%,GC 回收的内存不足 2% 时,会抛出这个异常。


5. OutOfMemoryError: direct memory


这个是向系统申请直接内存时,如果系统可用内存不足,就会抛出这个异常,对应的源代码Bits.java

static void reserveMemory(long size, int cap) {
    synchronized (Bits.class) {
        if (!memoryLimitSet && VM.isBooted()) {
            maxMemory = VM.maxDirectMemory();
            memoryLimitSet = true;
        }
        // -XX:MaxDirectMemorySize limits the total capacity rather than the
        // actual memory usage, which will differ when buffers are page
        // aligned.
        if (cap <= maxMemory - totalCapacity) {
            reservedMemory += size;
            totalCapacity += cap;
            count++;
            return;
        }
    }
    System.gc();
    try {
        Thread.sleep(100);
    } catch (InterruptedException x) {
        // Restore interrupt status
        Thread.currentThread().interrupt();
    }
    synchronized (Bits.class) {
        if (totalCapacity + cap > maxMemory)
            throw new OutOfMemoryError("Direct buffer memory");
        reservedMemory += size;
        totalCapacity += cap;
        count++;
    }
}

在 DirectByteBuffer 中,首先向 Bits 类申请额度,Bits 类有一个全局的 totalCapacity 变量,记录着全部 DirectByteBuffer 的总大小,每次申请,都先看看是否超限,堆外内存的限额默认与堆内内存(由 -Xmx 设定)相仿,可用 -XX:MaxDirectMemorySize 重新设定。


如果不指定,该参数的默认值为 Xmx 的值减去1个 Survior 区的值。 如设置启动参数 -Xmx20M -Xmn10M -XX:SurvivorRatio=8,那么申请 20M-1M=19M 的DirectMemory 如果已经超限,会主动执行 Sytem.gc(),期待能主动回收一点堆外内存。System.gc() 会触发一个 full gc,当然前提是你没有显示的设置 -XX:+DisableExplicitGC 来禁用显式GC。并且你需要知道,调用 System.gc() 并不能够保证 full gc 马上就能被执行。然后休眠一百毫秒,看看 totalCapacity 降下来没有,如果内存还是不足,就抛出 OOM 异常。如果额度被批准,就调用大名鼎鼎的sun.misc.Unsafe去分配内存,返回内存基地址

在发生这种异常时,一般通过 JMX 的java.nio.BufferPool.direct里面的属性去监控直接内存的变化以及使用(其实就是 BufferPoolMXBean ),来定位问题。


微信图片_20220624195028.jpg


6. OutOfMemoryError: map failed


这个是 File MMAP(文件映射内存)时,如果系统内存不足,就会抛出这个异常,对应的源代码是:

以 Linux 为例:

JNIEXPORT jlong JNICALL
Java_sun_nio_ch_FileChannelImpl_map0(JNIEnv *env, jobject this,
                                     jint prot, jlong off, jlong len)
{
    void *mapAddress = 0;
    jobject fdo = (*env)->GetObjectField(env, this, chan_fd);
    jint fd = fdval(env, fdo);
    int protections = 0;
    int flags = 0;
    if (prot == sun_nio_ch_FileChannelImpl_MAP_RO) {
        protections = PROT_READ;
        flags = MAP_SHARED;
    } else if (prot == sun_nio_ch_FileChannelImpl_MAP_RW) {
        protections = PROT_WRITE | PROT_READ;
        flags = MAP_SHARED;
    } else if (prot == sun_nio_ch_FileChannelImpl_MAP_PV) {
        protections =  PROT_WRITE | PROT_READ;
        flags = MAP_PRIVATE;
    }
    //调用mmap
    mapAddress = mmap64(
        0,                    /* Let OS decide location */
        len,                  /* Number of bytes to map */
        protections,          /* File permissions */
        flags,                /* Changes are shared */
        fd,                   /* File descriptor of mapped file */
        off);                 /* Offset into file */
    //内存不足时,抛出OutOfMemoryError
    if (mapAddress == MAP_FAILED) {
        if (errno == ENOMEM) {
            JNU_ThrowOutOfMemoryError(env, "Map failed");
            return IOS_THROWN;
        }
        return handle(env, -1, "Map failed");
    }
    return ((jlong) (unsigned long) mapAddress);
}

这种情况下,考虑:

  1. 增加系统内存
  2. 采用文件分块,不要一次 mmap 很大的文件,也就是减少每次 mmap 文件的大小


7. OutOfMemoryError: Requested array size exceeds VM limit


当申请的数组大小超过堆内存限制,就会抛出这个异常。


8. OutOfMemoryError: Metaspace


Metadata 占用空间超限(参考上面简述 Java 内存构成, class 这一块 包含两种,一种是 metadata,一种是 class space),会抛出这个异常,那么如何查看元空间内存呢?

可以通过两个命令,这两个输出是一样的:

  • jmap -clstats <PID>
  • jcmd <PID> GC.class_stats (这个需要启动参数: -XX:+UnlockDiagnosticVMOptions)
Index Super InstBytes KlassBytes annotations    CpAll MethodCount Bytecodes MethodAll    ROAll     RWAll     Total ClassName
    1    -1 214348176        504           0        0           0         0         0       24       616       640 [C
    2    -1  71683872        504           0        0           0         0         0       24       616       640 [B
    3    -1  53085688        504           0        0           0         0         0       24       616       640 [Ljava.lang.Object;
    4    -1  28135528        504           0        0           0         0         0       32       616       648 [Ljava.util.HashMap$Node;
    5 17478  12582216       1440           0     7008          64      2681     39040    11232     37248     48480 java.util.ArrayList
 .........
 25255    25         0        528           0      592           3        42       568      448      1448      1896 zipkin2.reporter.metrics.micrometer.MicrometerReporterMetrics$Builder
            472572680   16436464      283592 41813040      225990   8361510  75069552 39924272 101013144 140937416 Total
               335.3%      11.7%        0.2%    29.7%           -      5.9%     53.3%    28.3%     71.7%    100.0%
Index Super InstBytes KlassBytes annotations    CpAll MethodCount Bytecodes MethodAll    ROAll     RWAll     Total ClassName

其中,每个指标的含义如下所示:

  • InstBytes:实例占用大小
  • KlassBytes:类占用大小
  • annotations:注解占用大小
  • CpAll:常量池中占用大小
  • MethodCount:方法个数
  • Bytecodes:字节码大小
  • MethodAll:方法占用大小
  • ROAll:只读内存中内存占用
  • RWAll:读写内存中内存占用


9. OutOfMemoryError: Compressed class space


class space 内存溢出导致的,和上一个异常类似,需要查看类信息统计定位问题。


10. OutOfMemoryError: reason stack_trace_with_native_method


这个发生在 JNI 调用中,内存不足


相关文章
|
存储 Java
【面试题精讲】JVM-运行时数据区-StackOverFlowError
【面试题精讲】JVM-运行时数据区-StackOverFlowError
|
缓存 Java 大数据
深入解析JVM调优:解决OutOfMemoryError、内存泄露、线程死锁、锁争用和高CPU消耗问题
深入解析JVM调优:解决OutOfMemoryError、内存泄露、线程死锁、锁争用和高CPU消耗问题
224 0
|
存储 Java C++
【JVM】StackOverflowError与OutOfMemoryError
【JVM】StackOverflowError与OutOfMemoryError
113 0
【JVM】StackOverflowError与OutOfMemoryError
|
存储 Java 数据库连接
JVM之常见内存溢出(OutOfMemoryError)异常 ✨ 每日积累
JVM之常见内存溢出(OutOfMemoryError)异常 ✨ 每日积累
JVM之常见内存溢出(OutOfMemoryError)异常 ✨ 每日积累
|
Java
编写一个程序,它能导致JVM抛出一个OutOfMemoryError,然后捕获和处理这个异常
编写一个程序,它能导致JVM抛出一个OutOfMemoryError,然后捕获和处理这个异常
203 0
|
Java 编译器 Linux
JVM相关 - StackOverflowError 与 OutOfMemoryError(上)
JVM相关 - StackOverflowError 与 OutOfMemoryError(上)
JVM相关 - StackOverflowError 与 OutOfMemoryError(上)
|
存储 Java API
JVM 实战 OutOfMemoryError 异常
JVM 实战 OutOfMemoryError 异常
JVM 实战 OutOfMemoryError 异常
|
缓存 监控 Java
JVM源码分析之临门一脚的OutOfMemoryError完全解读
JVM源码分析之临门一脚的OutOfMemoryError完全解读
|
存储 Java API
JVM 实战 OutOfMemoryError 异常(下)
在《Java虚拟机规范》的规定里,除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OutOfMemoryError(下文称OOM)异常的可能。(本文主要是基于 jdk1.8 展开探讨)
155 0
|
1月前
|
存储 安全 Java
jvm 锁的 膨胀过程?锁内存怎么变化的
【10月更文挑战第3天】在Java虚拟机(JVM)中,`synchronized`关键字用于实现同步,确保多个线程在访问共享资源时的一致性和线程安全。JVM对`synchronized`进行了优化,以适应不同的竞争场景,这种优化主要体现在锁的膨胀过程,即从偏向锁到轻量级锁,再到重量级锁的转变。下面我们将详细介绍这一过程以及锁在内存中的变化。
37 4