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 ),来定位问题。
6. OutOfMemoryError: map failed
这个是 File MMAP(文件映射内存)时,如果系统内存不足,就会抛出这个异常,对应的源代码是:
- Windows:FileDispatcherImpl.c
- Linux:FileDispatcherImpl.c
以 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); }
这种情况下,考虑:
- 增加系统内存
- 采用文件分块,不要一次 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 调用中,内存不足